You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mina.apache.org by lg...@apache.org on 2018/09/06 16:03:53 UTC

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

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHKeyPairResourceParser.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHKeyPairResourceParser.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHKeyPairResourceParser.java
new file mode 100644
index 0000000..95db256
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHKeyPairResourceParser.java
@@ -0,0 +1,358 @@
+/*
+ * 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.loader.openssh;
+
+import java.io.ByteArrayInputStream;
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.StreamCorruptedException;
+import java.nio.charset.StandardCharsets;
+import java.security.GeneralSecurityException;
+import java.security.InvalidKeyException;
+import java.security.Key;
+import java.security.KeyPair;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.util.AbstractMap.SimpleImmutableEntry;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.TreeMap;
+
+import org.apache.sshd.common.config.keys.FilePasswordProvider;
+import org.apache.sshd.common.config.keys.KeyEntryResolver;
+import org.apache.sshd.common.config.keys.KeyUtils;
+import org.apache.sshd.common.config.keys.PrivateKeyEntryDecoder;
+import org.apache.sshd.common.config.keys.PublicKeyEntryDecoder;
+import org.apache.sshd.common.config.keys.loader.AbstractKeyPairResourceParser;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.ValidateUtils;
+import org.apache.sshd.common.util.buffer.BufferUtils;
+import org.apache.sshd.common.util.io.IoUtils;
+import org.apache.sshd.common.util.security.SecurityUtils;
+
+/**
+ * Basic support for <A HREF="http://cvsweb.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/PROTOCOL.key?rev=1.1&content-type=text/x-cvsweb-markup">OpenSSH key file(s)</A>
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class OpenSSHKeyPairResourceParser extends AbstractKeyPairResourceParser {
+    public static final String BEGIN_MARKER = "BEGIN OPENSSH PRIVATE KEY";
+    public static final List<String> BEGINNERS =
+            Collections.unmodifiableList(Collections.singletonList(BEGIN_MARKER));
+
+    public static final String END_MARKER = "END OPENSSH PRIVATE KEY";
+    public static final List<String> ENDERS =
+            Collections.unmodifiableList(Collections.singletonList(END_MARKER));
+
+    public static final String AUTH_MAGIC = "openssh-key-v1";
+    public static final OpenSSHKeyPairResourceParser INSTANCE = new OpenSSHKeyPairResourceParser();
+
+    private static final byte[] AUTH_MAGIC_BYTES = AUTH_MAGIC.getBytes(StandardCharsets.UTF_8);
+    private static final Map<String, PrivateKeyEntryDecoder<?, ?>> BY_KEY_TYPE_DECODERS_MAP =
+            new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
+
+    private static final Map<Class<?>, PrivateKeyEntryDecoder<?, ?>> BY_KEY_CLASS_DECODERS_MAP =
+            new HashMap<>();
+
+    static {
+        registerPrivateKeyEntryDecoder(OpenSSHRSAPrivateKeyDecoder.INSTANCE);
+        registerPrivateKeyEntryDecoder(OpenSSHDSSPrivateKeyEntryDecoder.INSTANCE);
+
+        if (SecurityUtils.isECCSupported()) {
+            registerPrivateKeyEntryDecoder(OpenSSHECDSAPrivateKeyEntryDecoder.INSTANCE);
+        }
+        if (SecurityUtils.isEDDSACurveSupported()) {
+            registerPrivateKeyEntryDecoder(SecurityUtils.getOpenSSHEDDSAPrivateKeyEntryDecoder());
+        }
+    }
+
+    public OpenSSHKeyPairResourceParser() {
+        super(BEGINNERS, ENDERS);
+    }
+
+    @Override
+    public Collection<KeyPair> extractKeyPairs(
+            String resourceKey, String beginMarker, String endMarker, FilePasswordProvider passwordProvider, InputStream stream)
+                    throws IOException, GeneralSecurityException {
+        stream = validateStreamMagicMarker(resourceKey, stream);
+
+        String cipher = KeyEntryResolver.decodeString(stream);
+        if (!OpenSSHParserContext.IS_NONE_CIPHER.test(cipher)) {
+            throw new NoSuchAlgorithmException("Unsupported cipher: " + cipher);
+        }
+
+        boolean debugEnabled = log.isDebugEnabled();
+        if (debugEnabled) {
+            log.debug("extractKeyPairs({}) cipher={}", resourceKey, cipher);
+        }
+
+        String kdfName = KeyEntryResolver.decodeString(stream);
+        if (!OpenSSHParserContext.IS_NONE_KDF.test(kdfName)) {
+            throw new NoSuchAlgorithmException("Unsupported KDF: " + kdfName);
+        }
+
+        byte[] kdfOptions = KeyEntryResolver.readRLEBytes(stream);
+        if (debugEnabled) {
+            log.debug("extractKeyPairs({}) KDF={}, options={}",
+                      resourceKey, kdfName, BufferUtils.toHex(':', kdfOptions));
+        }
+
+        int numKeys = KeyEntryResolver.decodeInt(stream);
+        if (numKeys <= 0) {
+            if (debugEnabled) {
+                log.debug("extractKeyPairs({}) no encoded keys", resourceKey);
+            }
+            return Collections.emptyList();
+        }
+
+        List<PublicKey> publicKeys = new ArrayList<>(numKeys);
+        OpenSSHParserContext context = new OpenSSHParserContext(cipher, kdfName, kdfOptions);
+        boolean traceEnabled = log.isTraceEnabled();
+        for (int index = 1; index <= numKeys; index++) {
+            PublicKey pubKey = readPublicKey(resourceKey, context, stream);
+            ValidateUtils.checkNotNull(pubKey, "Empty public key #%d in %s", index, resourceKey);
+            if (traceEnabled) {
+                log.trace("extractKeyPairs({}) read public key #{}: {} {}",
+                          resourceKey, index, KeyUtils.getKeyType(pubKey), KeyUtils.getFingerPrint(pubKey));
+            }
+            publicKeys.add(pubKey);
+        }
+
+        byte[] privateData = KeyEntryResolver.readRLEBytes(stream);
+        try (InputStream bais = new ByteArrayInputStream(privateData)) {
+            return readPrivateKeys(resourceKey, context, publicKeys, passwordProvider, bais);
+        }
+    }
+
+    protected PublicKey readPublicKey(
+            String resourceKey, OpenSSHParserContext context, InputStream stream)
+                    throws IOException, GeneralSecurityException {
+        byte[] keyData = KeyEntryResolver.readRLEBytes(stream);
+        try (InputStream bais = new ByteArrayInputStream(keyData)) {
+            String keyType = KeyEntryResolver.decodeString(bais);
+            PublicKeyEntryDecoder<?, ?> decoder = KeyUtils.getPublicKeyEntryDecoder(keyType);
+            if (decoder == null) {
+                throw new NoSuchAlgorithmException("Unsupported key type (" + keyType + ") in " + resourceKey);
+            }
+
+            return decoder.decodePublicKey(keyType, bais);
+        }
+    }
+
+    protected List<KeyPair> readPrivateKeys(
+            String resourceKey, OpenSSHParserContext context, Collection<? extends PublicKey> publicKeys,
+            FilePasswordProvider passwordProvider, InputStream stream)
+                    throws IOException, GeneralSecurityException {
+        if (GenericUtils.isEmpty(publicKeys)) {
+            return Collections.emptyList();
+        }
+
+        boolean traceEnabled = log.isTraceEnabled();
+        int check1 = KeyEntryResolver.decodeInt(stream);
+        int check2 = KeyEntryResolver.decodeInt(stream);
+        if (traceEnabled) {
+            log.trace("readPrivateKeys({}) check1=0x{}, check2=0x{}",
+                      resourceKey, Integer.toHexString(check1), Integer.toHexString(check2));
+        }
+
+        List<KeyPair> keyPairs = new ArrayList<>(publicKeys.size());
+        for (PublicKey pubKey : publicKeys) {
+            String pubType = KeyUtils.getKeyType(pubKey);
+            int keyIndex = keyPairs.size() + 1;
+            if (traceEnabled) {
+                log.trace("extractKeyPairs({}) read private key #{}: {}",
+                        resourceKey, keyIndex, pubType);
+            }
+
+            Map.Entry<PrivateKey, String> prvData = readPrivateKey(resourceKey, context, pubType, passwordProvider, stream);
+            PrivateKey prvKey = (prvData == null) ? null : prvData.getKey();
+            ValidateUtils.checkNotNull(prvKey, "Empty private key #%d in %s", keyIndex, resourceKey);
+
+            String prvType = KeyUtils.getKeyType(prvKey);
+            ValidateUtils.checkTrue(Objects.equals(pubType, prvType),
+                    "Mismatched public (%s) vs. private (%s) key type #%d in %s",
+                    pubType, prvType, keyIndex, resourceKey);
+
+            if (traceEnabled) {
+                log.trace("extractKeyPairs({}) add private key #{}: {} {}",
+                        resourceKey, keyIndex, prvType, prvData.getValue());
+            }
+            keyPairs.add(new KeyPair(pubKey, prvKey));
+        }
+
+        return keyPairs;
+    }
+
+    protected SimpleImmutableEntry<PrivateKey, String> readPrivateKey(
+            String resourceKey, OpenSSHParserContext context, String keyType, FilePasswordProvider passwordProvider, InputStream stream)
+                    throws IOException, GeneralSecurityException {
+        String prvType = KeyEntryResolver.decodeString(stream);
+        if (!Objects.equals(keyType, prvType)) {
+            throw new StreamCorruptedException("Mismatched private key type: "
+                    + ", expected=" + keyType + ", actual=" + prvType
+                    + " in " + resourceKey);
+        }
+
+        PrivateKeyEntryDecoder<?, ?> decoder = getPrivateKeyEntryDecoder(prvType);
+        if (decoder == null) {
+            throw new NoSuchAlgorithmException("Unsupported key type (" + prvType + ") in " + resourceKey);
+        }
+
+        PrivateKey prvKey = decoder.decodePrivateKey(prvType, passwordProvider, stream);
+        if (prvKey == null) {
+            throw new InvalidKeyException("Cannot parse key type (" + prvType + ") in " + resourceKey);
+        }
+
+        String comment = KeyEntryResolver.decodeString(stream);
+        return new SimpleImmutableEntry<>(prvKey, comment);
+    }
+
+    protected <S extends InputStream> S validateStreamMagicMarker(String resourceKey, S stream) throws IOException {
+        byte[] actual = new byte[AUTH_MAGIC_BYTES.length];
+        IoUtils.readFully(stream, actual);
+        if (!Arrays.equals(AUTH_MAGIC_BYTES, actual)) {
+            throw new StreamCorruptedException(resourceKey + ": Mismatched magic marker value: " + BufferUtils.toHex(':', actual));
+        }
+
+        int eos = stream.read();
+        if (eos == -1) {
+            throw new EOFException(resourceKey + ": Premature EOF after magic marker value");
+        }
+
+        if (eos != 0) {
+            throw new StreamCorruptedException(resourceKey + ": Missing EOS after magic marker value: 0x" + Integer.toHexString(eos));
+        }
+
+        return stream;
+    }
+
+    /**
+     * @param decoder The decoder to register
+     * @throws IllegalArgumentException if no decoder or not key type or no
+     *                                  supported names for the decoder
+     * @see PrivateKeyEntryDecoder#getPublicKeyType()
+     * @see PrivateKeyEntryDecoder#getSupportedTypeNames()
+     */
+    public static void registerPrivateKeyEntryDecoder(PrivateKeyEntryDecoder<?, ?> decoder) {
+        Objects.requireNonNull(decoder, "No decoder specified");
+
+        Class<?> pubType = Objects.requireNonNull(decoder.getPublicKeyType(), "No public key type declared");
+        Class<?> prvType = Objects.requireNonNull(decoder.getPrivateKeyType(), "No private key type declared");
+        synchronized (BY_KEY_CLASS_DECODERS_MAP) {
+            BY_KEY_CLASS_DECODERS_MAP.put(pubType, decoder);
+            BY_KEY_CLASS_DECODERS_MAP.put(prvType, decoder);
+        }
+
+        Collection<String> names = ValidateUtils.checkNotNullAndNotEmpty(decoder.getSupportedTypeNames(), "No supported key type");
+        synchronized (BY_KEY_TYPE_DECODERS_MAP) {
+            for (String n : names) {
+                PrivateKeyEntryDecoder<?, ?> prev = BY_KEY_TYPE_DECODERS_MAP.put(n, decoder);
+                if (prev != null) {
+                    //noinspection UnnecessaryContinue
+                    continue;   // debug breakpoint
+                }
+            }
+        }
+    }
+
+    /**
+     * @param keyType The {@code OpenSSH} key type string -  e.g., {@code ssh-rsa, ssh-dss}
+     *                - ignored if {@code null}/empty
+     * @return The registered {@link PrivateKeyEntryDecoder} or {code null} if not found
+     */
+    public static PrivateKeyEntryDecoder<?, ?> getPrivateKeyEntryDecoder(String keyType) {
+        if (GenericUtils.isEmpty(keyType)) {
+            return null;
+        }
+
+        synchronized (BY_KEY_TYPE_DECODERS_MAP) {
+            return BY_KEY_TYPE_DECODERS_MAP.get(keyType);
+        }
+    }
+
+    /**
+     * @param kp The {@link KeyPair} to examine - ignored if {@code null}
+     * @return The matching {@link PrivateKeyEntryDecoder} provided <U>both</U>
+     * the public and private keys have the same decoder - {@code null} if no
+     * match found
+     * @see #getPrivateKeyEntryDecoder(Key)
+     */
+    public static PrivateKeyEntryDecoder<?, ?> getPrivateKeyEntryDecoder(KeyPair kp) {
+        if (kp == null) {
+            return null;
+        }
+
+        PrivateKeyEntryDecoder<?, ?> d1 = getPrivateKeyEntryDecoder(kp.getPublic());
+        PrivateKeyEntryDecoder<?, ?> d2 = getPrivateKeyEntryDecoder(kp.getPrivate());
+        if (d1 == d2) {
+            return d1;
+        } else {
+            return null;    // some kind of mixed keys...
+        }
+    }
+
+    /**
+     * @param key The {@link Key} (public or private) - ignored if {@code null}
+     * @return The registered {@link PrivateKeyEntryDecoder} for this key or {code null} if no match found
+     * @see #getPrivateKeyEntryDecoder(Class)
+     */
+    public static PrivateKeyEntryDecoder<?, ?> getPrivateKeyEntryDecoder(Key key) {
+        if (key == null) {
+            return null;
+        } else {
+            return getPrivateKeyEntryDecoder(key.getClass());
+        }
+    }
+
+    /**
+     * @param keyType The key {@link Class} - ignored if {@code null} or not a {@link Key}
+     *                compatible type
+     * @return The registered {@link PrivateKeyEntryDecoder} or {code null} if no match found
+     */
+    public static PrivateKeyEntryDecoder<?, ?> getPrivateKeyEntryDecoder(Class<?> keyType) {
+        if ((keyType == null) || (!Key.class.isAssignableFrom(keyType))) {
+            return null;
+        }
+
+        synchronized (BY_KEY_TYPE_DECODERS_MAP) {
+            PrivateKeyEntryDecoder<?, ?> decoder = BY_KEY_CLASS_DECODERS_MAP.get(keyType);
+            if (decoder != null) {
+                return decoder;
+            }
+
+            // in case it is a derived class
+            for (PrivateKeyEntryDecoder<?, ?> dec : BY_KEY_CLASS_DECODERS_MAP.values()) {
+                Class<?> pubType = dec.getPublicKeyType();
+                Class<?> prvType = dec.getPrivateKeyType();
+                if (pubType.isAssignableFrom(keyType) || prvType.isAssignableFrom(keyType)) {
+                    return dec;
+                }
+            }
+        }
+
+        return null;
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHParserContext.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHParserContext.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHParserContext.java
new file mode 100644
index 0000000..07f2a9a
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHParserContext.java
@@ -0,0 +1,83 @@
+/*
+ * 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.loader.openssh;
+
+import java.util.function.Predicate;
+
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.buffer.BufferUtils;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class OpenSSHParserContext {
+    public static final String NONE_CIPHER = "none";
+    public static final Predicate<String> IS_NONE_CIPHER = c -> GenericUtils.isEmpty(c) || NONE_CIPHER.equalsIgnoreCase(c);
+
+    public static final String NONE_KDF = "none";
+    public static final Predicate<String> IS_NONE_KDF = c -> GenericUtils.isEmpty(c) || NONE_KDF.equalsIgnoreCase(c);
+
+    private String cipherName;
+    private String kdfName;
+    private byte[] kdfOptions;
+
+    public OpenSSHParserContext() {
+        super();
+    }
+
+    public OpenSSHParserContext(String cipherName, String kdfName, byte... kdfOptions) {
+        this.cipherName = cipherName;
+        this.kdfName = kdfName;
+        this.kdfOptions = kdfOptions;
+    }
+
+    public String getCipherName() {
+        return cipherName;
+    }
+
+    public void setCipherName(String cipherName) {
+        this.cipherName = cipherName;
+    }
+
+    public String getKdfName() {
+        return kdfName;
+    }
+
+    public void setKdfName(String kdfName) {
+        this.kdfName = kdfName;
+    }
+
+    public byte[] getKdfOptions() {
+        return kdfOptions;
+    }
+
+    public void setKdfOptions(byte[] kdfOptions) {
+        this.kdfOptions = kdfOptions;
+    }
+
+    @Override
+    public String toString() {
+        return getClass().getSimpleName()
+            + "[cipher=" + getCipherName()
+            + ", kdfName=" + getKdfName()
+            + ", kdfOptions=" + BufferUtils.toHex(':', getKdfOptions())
+            + "]";
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHRSAPrivateKeyDecoder.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHRSAPrivateKeyDecoder.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHRSAPrivateKeyDecoder.java
new file mode 100644
index 0000000..72e003f
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHRSAPrivateKeyDecoder.java
@@ -0,0 +1,135 @@
+/*
+ * 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.loader.openssh;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.math.BigInteger;
+import java.security.GeneralSecurityException;
+import java.security.InvalidKeyException;
+import java.security.KeyFactory;
+import java.security.KeyPairGenerator;
+import java.security.interfaces.RSAPrivateCrtKey;
+import java.security.interfaces.RSAPrivateKey;
+import java.security.interfaces.RSAPublicKey;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.RSAPrivateCrtKeySpec;
+import java.security.spec.RSAPrivateKeySpec;
+import java.security.spec.RSAPublicKeySpec;
+import java.util.Collections;
+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.KeyUtils;
+import org.apache.sshd.common.config.keys.impl.AbstractPrivateKeyEntryDecoder;
+import org.apache.sshd.common.keyprovider.KeyPairProvider;
+import org.apache.sshd.common.util.security.SecurityUtils;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class OpenSSHRSAPrivateKeyDecoder extends AbstractPrivateKeyEntryDecoder<RSAPublicKey, RSAPrivateKey> {
+    public static final BigInteger DEFAULT_PUBLIC_EXPONENT = new BigInteger("65537");
+    public static final OpenSSHRSAPrivateKeyDecoder INSTANCE = new OpenSSHRSAPrivateKeyDecoder();
+
+    public OpenSSHRSAPrivateKeyDecoder() {
+        super(RSAPublicKey.class, RSAPrivateKey.class, Collections.unmodifiableList(Collections.singletonList(KeyPairProvider.SSH_RSA)));
+    }
+
+    @Override
+    public RSAPrivateKey decodePrivateKey(String keyType, FilePasswordProvider passwordProvider, InputStream keyData)
+            throws IOException, GeneralSecurityException {
+        if (!KeyPairProvider.SSH_RSA.equals(keyType)) { // just in case we were invoked directly
+            throw new InvalidKeySpecException("Unexpected key type: " + keyType);
+        }
+
+        BigInteger n = KeyEntryResolver.decodeBigInt(keyData);
+        BigInteger e = KeyEntryResolver.decodeBigInt(keyData);
+        if (!Objects.equals(e, DEFAULT_PUBLIC_EXPONENT)) {
+            log.warn("decodePrivateKey({}) non-standard RSA exponent found: {}", keyType, e);
+        }
+
+        BigInteger d = KeyEntryResolver.decodeBigInt(keyData);
+        BigInteger inverseQmodP = KeyEntryResolver.decodeBigInt(keyData);
+        Objects.requireNonNull(inverseQmodP, "Missing iqmodp"); // TODO run some validation on it
+        BigInteger p = KeyEntryResolver.decodeBigInt(keyData);
+        BigInteger q = KeyEntryResolver.decodeBigInt(keyData);
+        BigInteger modulus = p.multiply(q);
+        if (!Objects.equals(n, modulus)) {
+            log.warn("decodePrivateKey({}) mismatched modulus values: encoded={}, calculated={}",
+                     keyType, n, modulus);
+        }
+
+        return generatePrivateKey(new RSAPrivateKeySpec(n, d));
+    }
+
+    @Override
+    public boolean isPublicKeyRecoverySupported() {
+        return true;
+    }
+
+    @Override
+    public RSAPublicKey recoverPublicKey(RSAPrivateKey privateKey) throws GeneralSecurityException {
+        return KeyUtils.recoverRSAPublicKey(privateKey);
+    }
+
+    @Override
+    public RSAPublicKey clonePublicKey(RSAPublicKey key) throws GeneralSecurityException {
+        if (key == null) {
+            return null;
+        } else {
+            return generatePublicKey(new RSAPublicKeySpec(key.getModulus(), key.getPublicExponent()));
+        }
+    }
+
+    @Override
+    public RSAPrivateKey clonePrivateKey(RSAPrivateKey key) throws GeneralSecurityException {
+        if (key == null) {
+            return null;
+        }
+
+        if (!(key instanceof RSAPrivateCrtKey)) {
+            throw new InvalidKeyException("Cannot clone a non-RSAPrivateCrtKey: " + key.getClass().getSimpleName());
+        }
+
+        RSAPrivateCrtKey rsaPrv = (RSAPrivateCrtKey) key;
+        return generatePrivateKey(
+                new RSAPrivateCrtKeySpec(
+                        rsaPrv.getModulus(),
+                        rsaPrv.getPublicExponent(),
+                        rsaPrv.getPrivateExponent(),
+                        rsaPrv.getPrimeP(),
+                        rsaPrv.getPrimeQ(),
+                        rsaPrv.getPrimeExponentP(),
+                        rsaPrv.getPrimeExponentQ(),
+                        rsaPrv.getCrtCoefficient()));
+    }
+
+    @Override
+    public KeyPairGenerator getKeyPairGenerator() throws GeneralSecurityException {
+        return SecurityUtils.getKeyPairGenerator(KeyUtils.RSA_ALGORITHM);
+    }
+
+    @Override
+    public KeyFactory getKeyFactoryInstance() throws GeneralSecurityException {
+        return SecurityUtils.getKeyFactory(KeyUtils.RSA_ALGORITHM);
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/AbstractPEMResourceKeyPairParser.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/AbstractPEMResourceKeyPairParser.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/AbstractPEMResourceKeyPairParser.java
new file mode 100644
index 0000000..bee13d6
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/AbstractPEMResourceKeyPairParser.java
@@ -0,0 +1,167 @@
+/*
+ * 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.loader.pem;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.StreamCorruptedException;
+import java.security.GeneralSecurityException;
+import java.security.KeyPair;
+import java.security.NoSuchAlgorithmException;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+import javax.security.auth.login.CredentialException;
+import javax.security.auth.login.FailedLoginException;
+
+import org.apache.sshd.common.config.keys.FilePasswordProvider;
+import org.apache.sshd.common.config.keys.loader.AbstractKeyPairResourceParser;
+import org.apache.sshd.common.config.keys.loader.KeyPairResourceParser;
+import org.apache.sshd.common.config.keys.loader.PrivateKeyEncryptionContext;
+import org.apache.sshd.common.config.keys.loader.PrivateKeyObfuscator;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.ValidateUtils;
+import org.apache.sshd.common.util.buffer.BufferUtils;
+
+/**
+ * Base class for PEM file key-pair loaders
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public abstract class AbstractPEMResourceKeyPairParser
+        extends AbstractKeyPairResourceParser
+        implements KeyPairPEMResourceParser {
+    private final String algo;
+    private final String algId;
+
+    protected AbstractPEMResourceKeyPairParser(String algo, String algId, List<String> beginners, List<String> enders) {
+        super(beginners, enders);
+        this.algo = ValidateUtils.checkNotNullAndNotEmpty(algo, "No encryption algorithm provided");
+        this.algId = ValidateUtils.checkNotNullAndNotEmpty(algId, "No algorithm identifier provided");
+    }
+
+    @Override
+    public String getAlgorithm() {
+        return algo;
+    }
+
+    @Override
+    public String getAlgorithmIdentifier() {
+        return algId;
+    }
+
+    @Override
+    public Collection<KeyPair> extractKeyPairs(
+            String resourceKey, String beginMarker, String endMarker, FilePasswordProvider passwordProvider, List<String> lines)
+                    throws IOException, GeneralSecurityException {
+        if (GenericUtils.isEmpty(lines)) {
+            return Collections.emptyList();
+        }
+
+        Boolean encrypted = null;
+        byte[] initVector = null;
+        String algInfo = null;
+        int dataStartIndex = -1;
+        for (int index = 0; index < lines.size(); index++) {
+            String line = GenericUtils.trimToEmpty(lines.get(index));
+            if (GenericUtils.isEmpty(line)) {
+                continue;
+            }
+
+            // check if header line - if not, assume data lines follow
+            int headerPos = line.indexOf(':');
+            if (headerPos < 0) {
+                dataStartIndex = index;
+                break;
+            }
+
+            if (line.startsWith("Proc-Type:")) {
+                if (encrypted != null) {
+                    throw new StreamCorruptedException("Multiple encryption indicators in " + resourceKey);
+                }
+
+                line = line.substring(headerPos + 1).trim();
+                line = line.toUpperCase();
+                encrypted = Boolean.valueOf(line.contains("ENCRYPTED"));
+            } else if (line.startsWith("DEK-Info:")) {
+                if ((initVector != null) || (algInfo != null)) {
+                    throw new StreamCorruptedException("Multiple encryption settings in " + resourceKey);
+                }
+
+                line = line.substring(headerPos + 1).trim();
+                headerPos = line.indexOf(',');
+                if (headerPos < 0) {
+                    throw new StreamCorruptedException(resourceKey + ": Missing encryption data values separator in line '" + line + "'");
+                }
+
+                algInfo = line.substring(0, headerPos).trim();
+
+                String algInitVector = line.substring(headerPos + 1).trim();
+                initVector = BufferUtils.decodeHex(BufferUtils.EMPTY_HEX_SEPARATOR, algInitVector);
+            }
+        }
+
+        if (dataStartIndex < 0) {
+            throw new StreamCorruptedException("No data lines (only headers or empty) found in " + resourceKey);
+        }
+
+        List<String> dataLines = lines.subList(dataStartIndex, lines.size());
+        if ((encrypted != null) || (algInfo != null) || (initVector != null)) {
+            if (passwordProvider == null) {
+                throw new CredentialException("Missing password provider for encrypted resource=" + resourceKey);
+            }
+
+            String password = passwordProvider.getPassword(resourceKey);
+            if (GenericUtils.isEmpty(password)) {
+                throw new FailedLoginException("No password data for encrypted resource=" + resourceKey);
+            }
+
+            PrivateKeyEncryptionContext encContext = new PrivateKeyEncryptionContext(algInfo);
+            encContext.setPassword(password);
+            encContext.setInitVector(initVector);
+            byte[] encryptedData = KeyPairResourceParser.extractDataBytes(dataLines);
+            byte[] decodedData = applyPrivateKeyCipher(encryptedData, encContext, false);
+            try (InputStream bais = new ByteArrayInputStream(decodedData)) {
+                return extractKeyPairs(resourceKey, beginMarker, endMarker, passwordProvider, bais);
+            }
+        }
+
+        return super.extractKeyPairs(resourceKey, beginMarker, endMarker, passwordProvider, dataLines);
+    }
+
+    protected byte[] applyPrivateKeyCipher(byte[] bytes, PrivateKeyEncryptionContext encContext, boolean encryptIt) throws GeneralSecurityException {
+        String cipherName = encContext.getCipherName();
+        PrivateKeyObfuscator o = encContext.resolvePrivateKeyObfuscator();
+        if (o == null) {
+            throw new NoSuchAlgorithmException("decryptPrivateKeyData(" + encContext + ")[encrypt=" + encryptIt + "] unknown cipher: " + cipherName);
+        }
+
+        if (encryptIt) {
+            byte[]  initVector = encContext.getInitVector();
+            if (GenericUtils.isEmpty(initVector)) {
+                initVector = o.generateInitializationVector(encContext);
+                encContext.setInitVector(initVector);
+            }
+        }
+
+        return o.applyPrivateKeyCipher(bytes, encContext, encryptIt);
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/DSSPEMResourceKeyPairParser.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/DSSPEMResourceKeyPairParser.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/DSSPEMResourceKeyPairParser.java
new file mode 100644
index 0000000..0c357af
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/DSSPEMResourceKeyPairParser.java
@@ -0,0 +1,126 @@
+/*
+ * 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.loader.pem;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.StreamCorruptedException;
+import java.math.BigInteger;
+import java.security.GeneralSecurityException;
+import java.security.KeyFactory;
+import java.security.KeyPair;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.spec.DSAPrivateKeySpec;
+import java.security.spec.DSAPublicKeySpec;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.sshd.common.config.keys.FilePasswordProvider;
+import org.apache.sshd.common.config.keys.KeyUtils;
+import org.apache.sshd.common.util.io.NoCloseInputStream;
+import org.apache.sshd.common.util.io.der.ASN1Object;
+import org.apache.sshd.common.util.io.der.ASN1Type;
+import org.apache.sshd.common.util.io.der.DERParser;
+import org.apache.sshd.common.util.security.SecurityUtils;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class DSSPEMResourceKeyPairParser extends AbstractPEMResourceKeyPairParser {
+    // Not exactly according to standard but good enough
+    public static final String BEGIN_MARKER = "BEGIN DSA PRIVATE KEY";
+    public static final List<String> BEGINNERS =
+            Collections.unmodifiableList(Collections.singletonList(BEGIN_MARKER));
+
+    public static final String END_MARKER = "END DSA PRIVATE KEY";
+    public static final List<String> ENDERS =
+            Collections.unmodifiableList(Collections.singletonList(END_MARKER));
+
+    /**
+     * @see <A HREF="https://tools.ietf.org/html/rfc3279#section-2.3.2">RFC-3279 section 2.3.2</A>
+     */
+    public static final String DSS_OID = "1.2.840.10040.4.1";
+
+    public static final DSSPEMResourceKeyPairParser INSTANCE = new DSSPEMResourceKeyPairParser();
+
+    public DSSPEMResourceKeyPairParser() {
+        super(KeyUtils.DSS_ALGORITHM, DSS_OID, BEGINNERS, ENDERS);
+    }
+
+    @Override
+    public Collection<KeyPair> extractKeyPairs(
+            String resourceKey, String beginMarker, String endMarker, FilePasswordProvider passwordProvider, InputStream stream)
+                    throws IOException, GeneralSecurityException {
+        KeyPair kp = decodeDSSKeyPair(SecurityUtils.getKeyFactory(KeyUtils.DSS_ALGORITHM), stream, false);
+        return Collections.singletonList(kp);
+    }
+
+    /**
+     * <p>The ASN.1 syntax for the private key:</P>
+     * <pre><code>
+     * DSAPrivateKey ::= SEQUENCE {
+     *      version Version,
+     *      p       INTEGER,
+     *      q       INTEGER,
+     *      g       INTEGER,
+     *      y       INTEGER,
+     *      x       INTEGER
+     * }
+     * </code></pre>
+     * @param kf The {@link KeyFactory} To use to generate the keys
+     * @param s The {@link InputStream} containing the encoded bytes
+     * @param okToClose <code>true</code> if the method may close the input
+     * stream regardless of success or failure
+     * @return The recovered {@link KeyPair}
+     * @throws IOException If failed to read or decode the bytes
+     * @throws GeneralSecurityException If failed to generate the keys
+     */
+    public static KeyPair decodeDSSKeyPair(KeyFactory kf, InputStream s, boolean okToClose)
+            throws IOException, GeneralSecurityException {
+        ASN1Object sequence;
+        try (DERParser parser = new DERParser(NoCloseInputStream.resolveInputStream(s, okToClose))) {
+            sequence = parser.readObject();
+        }
+
+        if (!ASN1Type.SEQUENCE.equals(sequence.getObjType())) {
+            throw new IOException("Invalid DER: not a sequence: " + sequence.getObjType());
+        }
+
+        // Parse inside the sequence
+        try (DERParser parser = sequence.createParser()) {
+            // Skip version
+            ASN1Object version = parser.readObject();
+            if (version == null) {
+                throw new StreamCorruptedException("No version");
+            }
+
+            BigInteger p = parser.readObject().asInteger();
+            BigInteger q = parser.readObject().asInteger();
+            BigInteger g = parser.readObject().asInteger();
+            BigInteger y = parser.readObject().asInteger();
+            BigInteger x = parser.readObject().asInteger();
+            PublicKey pubKey = kf.generatePublic(new DSAPublicKeySpec(y, p, q, g));
+            PrivateKey prvKey = kf.generatePrivate(new DSAPrivateKeySpec(x, p, q, g));
+            return new KeyPair(pubKey, prvKey);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/ECDSAPEMResourceKeyPairParser.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/ECDSAPEMResourceKeyPairParser.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/ECDSAPEMResourceKeyPairParser.java
new file mode 100644
index 0000000..dd8d2ea
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/ECDSAPEMResourceKeyPairParser.java
@@ -0,0 +1,220 @@
+/*
+ * 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.loader.pem;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.StreamCorruptedException;
+import java.math.BigInteger;
+import java.security.GeneralSecurityException;
+import java.security.KeyFactory;
+import java.security.KeyPair;
+import java.security.NoSuchProviderException;
+import java.security.interfaces.ECPrivateKey;
+import java.security.interfaces.ECPublicKey;
+import java.security.spec.ECPoint;
+import java.security.spec.ECPrivateKeySpec;
+import java.security.spec.ECPublicKeySpec;
+import java.util.AbstractMap.SimpleImmutableEntry;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.sshd.common.cipher.ECCurves;
+import org.apache.sshd.common.config.keys.FilePasswordProvider;
+import org.apache.sshd.common.config.keys.KeyUtils;
+import org.apache.sshd.common.util.io.NoCloseInputStream;
+import org.apache.sshd.common.util.io.der.ASN1Object;
+import org.apache.sshd.common.util.io.der.ASN1Type;
+import org.apache.sshd.common.util.io.der.DERParser;
+import org.apache.sshd.common.util.security.SecurityUtils;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class ECDSAPEMResourceKeyPairParser extends AbstractPEMResourceKeyPairParser {
+    public static final String BEGIN_MARKER = "BEGIN EC PRIVATE KEY";
+    public static final List<String> BEGINNERS =
+            Collections.unmodifiableList(Collections.singletonList(BEGIN_MARKER));
+
+    public static final String END_MARKER = "END EC PRIVATE KEY";
+    public static final List<String> ENDERS =
+            Collections.unmodifiableList(Collections.singletonList(END_MARKER));
+
+    /**
+     * @see <A HREF="https://tools.ietf.org/html/rfc3279#section-2.3.5">RFC-3279 section 2.3.5</A>
+     */
+    public static final String ECDSA_OID = "1.2.840.10045.2.1";
+
+    public static final ECDSAPEMResourceKeyPairParser INSTANCE = new ECDSAPEMResourceKeyPairParser();
+
+    public ECDSAPEMResourceKeyPairParser() {
+        super(KeyUtils.EC_ALGORITHM, ECDSA_OID, BEGINNERS, ENDERS);
+    }
+
+    @Override
+    public Collection<KeyPair> extractKeyPairs(
+            String resourceKey, String beginMarker, String endMarker, FilePasswordProvider passwordProvider, InputStream stream)
+                    throws IOException, GeneralSecurityException {
+        Map.Entry<ECPublicKeySpec, ECPrivateKeySpec> spec = decodeECPrivateKeySpec(stream, false);
+        if (!SecurityUtils.isECCSupported()) {
+            throw new NoSuchProviderException("ECC not supported");
+        }
+
+        KeyFactory kf = SecurityUtils.getKeyFactory(KeyUtils.EC_ALGORITHM);
+        ECPublicKey pubKey = (ECPublicKey) kf.generatePublic(spec.getKey());
+        ECPrivateKey prvKey = (ECPrivateKey) kf.generatePrivate(spec.getValue());
+        KeyPair kp = new KeyPair(pubKey, prvKey);
+        return Collections.singletonList(kp);
+    }
+
+    /**
+     * <P>ASN.1 syntax according to rfc5915 is:</P></BR>
+     * <PRE><CODE>
+     * ECPrivateKey ::= SEQUENCE {
+     *      version        INTEGER { ecPrivkeyVer1(1) } (ecPrivkeyVer1),
+     *      privateKey     OCTET STRING,
+     *      parameters [0] ECParameters {{ NamedCurve }} OPTIONAL,
+     *      publicKey  [1] BIT STRING OPTIONAL
+     * }
+     * </CODE></PRE>
+     * <P><I>ECParameters</I> syntax according to RFC5480:</P></BR>
+     * <PRE><CODE>
+     * ECParameters ::= CHOICE {
+     *      namedCurve         OBJECT IDENTIFIER
+     *      -- implicitCurve   NULL
+     *      -- specifiedCurve  SpecifiedECDomain
+     * }
+     * </CODE></PRE>
+     * @param inputStream The {@link InputStream} containing the DER encoded data
+     * @param okToClose {@code true} if OK to close the DER stream once parsing complete
+     * @return The decoded {@link SimpleImmutableEntry} of {@link ECPublicKeySpec} and {@link ECPrivateKeySpec}
+     * @throws IOException If failed to to decode the DER stream
+     */
+    public static SimpleImmutableEntry<ECPublicKeySpec, ECPrivateKeySpec> decodeECPrivateKeySpec(InputStream inputStream, boolean okToClose) throws IOException {
+        ASN1Object sequence;
+        try (DERParser parser = new DERParser(NoCloseInputStream.resolveInputStream(inputStream, okToClose))) {
+            sequence = parser.readObject();
+        }
+
+        if (!ASN1Type.SEQUENCE.equals(sequence.getObjType())) {
+            throw new IOException("Invalid DER: not a sequence: " + sequence.getObjType());
+        }
+
+        // Parse inside the sequence
+        try (DERParser parser = sequence.createParser()) {
+            ECPrivateKeySpec prvSpec = decodeECPrivateKeySpec(parser);
+            ECCurves curve = ECCurves.fromCurveParameters(prvSpec.getParams());
+            if (curve == null) {
+                throw new StreamCorruptedException("Unknown curve");
+            }
+
+            ECPoint w = decodeECPublicKeyValue(curve, parser);
+            ECPublicKeySpec pubSpec = new ECPublicKeySpec(w, prvSpec.getParams());
+            return new SimpleImmutableEntry<>(pubSpec, prvSpec);
+        }
+    }
+
+    public static final ECPrivateKeySpec decodeECPrivateKeySpec(DERParser parser) throws IOException {
+        // see openssl asn1parse -inform PEM -in ...file... -dump
+        ASN1Object versionObject = parser.readObject(); // Skip version
+        if (versionObject == null) {
+            throw new StreamCorruptedException("No version");
+        }
+
+        // as per RFC-5915 section 3
+        BigInteger version = versionObject.asInteger();
+        if (!BigInteger.ONE.equals(version)) {
+            throw new StreamCorruptedException("Bad version value: " + version);
+        }
+
+        ASN1Object keyObject = parser.readObject();
+        if (keyObject == null) {
+            throw new StreamCorruptedException("No private key value");
+        }
+
+        ASN1Type objType = keyObject.getObjType();
+        if (!ASN1Type.OCTET_STRING.equals(objType)) {
+            throw new StreamCorruptedException("Non-matching private key object type: " + objType);
+        }
+
+        ASN1Object paramsObject = parser.readObject();
+        if (paramsObject == null) {
+            throw new StreamCorruptedException("No parameters value");
+        }
+
+        // TODO make sure params object tag is 0xA0
+
+        final List<Integer> curveOID;
+        try (DERParser paramsParser = paramsObject.createParser()) {
+            ASN1Object namedCurve = paramsParser.readObject();
+            if (namedCurve == null) {
+                throw new StreamCorruptedException("Missing named curve parameter");
+            }
+
+            curveOID = namedCurve.asOID();
+        }
+
+        ECCurves curve = ECCurves.fromOIDValue(curveOID);
+        if (curve == null) {
+            throw new StreamCorruptedException("Unknown curve OID: " + curveOID);
+        }
+
+        BigInteger s = ECCurves.octetStringToInteger(keyObject.getPureValueBytes());
+        return new ECPrivateKeySpec(s, curve.getParameters());
+    }
+
+    /**
+     * <P>ASN.1 syntax according to rfc5915 is:</P></BR>
+     * <PRE>
+     *      publicKey  [1] BIT STRING OPTIONAL
+     * </PRE>
+     * @param curve The {@link ECCurves} curve
+     * @param parser The {@link DERParser} assumed to be positioned at the
+     * start of the data
+     * @return The encoded {@link ECPoint}
+     * @throws IOException If failed to create the point
+     */
+    public static final ECPoint decodeECPublicKeyValue(ECCurves curve, DERParser parser) throws IOException {
+        // see openssl asn1parse -inform PEM -in ...file... -dump
+        ASN1Object dataObject = parser.readObject();
+        if (dataObject == null) {
+            throw new StreamCorruptedException("No public key data bytes");
+        }
+
+        try (DERParser dataParser = dataObject.createParser()) {
+            ASN1Object pointData = dataParser.readObject();
+            if (pointData == null) {
+                throw new StreamCorruptedException("Missing public key data parameter");
+            }
+
+            ASN1Type objType = pointData.getObjType();
+            if (!ASN1Type.BIT_STRING.equals(objType)) {
+                throw new StreamCorruptedException("Non-matching public key object type: " + objType);
+            }
+
+            // see https://tools.ietf.org/html/rfc5480#section-2.2
+            byte[] octets = pointData.getValue();
+            return ECCurves.octetStringToEcPoint(octets);
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/KeyPairPEMResourceParser.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/KeyPairPEMResourceParser.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/KeyPairPEMResourceParser.java
new file mode 100644
index 0000000..07c7e44
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/KeyPairPEMResourceParser.java
@@ -0,0 +1,37 @@
+/*
+ * 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.loader.pem;
+
+import org.apache.sshd.common.config.keys.loader.KeyPairResourceParser;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public interface KeyPairPEMResourceParser extends KeyPairResourceParser {
+    /**
+     * @return The encryption algorithm name - e.g., &quot;RSA&quot;, &quot;DSA&quot;
+     */
+    String getAlgorithm();
+
+    /**
+     * @return The OID used to identify this algorithm in DER encodings - e.g., RSA=1.2.840.113549.1.1.1
+     */
+    String getAlgorithmIdentifier();
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/PEMResourceParserUtils.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/PEMResourceParserUtils.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/PEMResourceParserUtils.java
new file mode 100644
index 0000000..b6749da
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/PEMResourceParserUtils.java
@@ -0,0 +1,109 @@
+/*
+ * 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.loader.pem;
+
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+import java.security.KeyPair;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.TreeMap;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.apache.sshd.common.config.keys.FilePasswordProvider;
+import org.apache.sshd.common.config.keys.loader.KeyPairResourceParser;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.ValidateUtils;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public final class PEMResourceParserUtils {
+    public static final KeyPairResourceParser PROXY = new KeyPairResourceParser() {
+        @Override
+        public Collection<KeyPair> loadKeyPairs(
+                String resourceKey, FilePasswordProvider passwordProvider, List<String> lines)
+                        throws IOException, GeneralSecurityException {
+            @SuppressWarnings("synthetic-access")
+            KeyPairResourceParser proxy = PROXY_HOLDER.get();
+            return (proxy == null) ? Collections.<KeyPair>emptyList() : proxy.loadKeyPairs(resourceKey, passwordProvider, lines);
+        }
+
+        @Override
+        public boolean canExtractKeyPairs(String resourceKey, List<String> lines)
+                throws IOException, GeneralSecurityException {
+            @SuppressWarnings("synthetic-access")
+            KeyPairResourceParser proxy = PROXY_HOLDER.get();
+            return (proxy != null) && proxy.canExtractKeyPairs(resourceKey, lines);
+        }
+    };
+
+    private static final Map<String, KeyPairPEMResourceParser> BY_OID_MAP = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
+    private static final Map<String, KeyPairPEMResourceParser> BY_ALGORITHM_MAP = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
+    private static final AtomicReference<KeyPairResourceParser> PROXY_HOLDER = new AtomicReference<>(KeyPairResourceParser.EMPTY);
+
+    static {
+        registerPEMResourceParser(RSAPEMResourceKeyPairParser.INSTANCE);
+        registerPEMResourceParser(DSSPEMResourceKeyPairParser.INSTANCE);
+        registerPEMResourceParser(ECDSAPEMResourceKeyPairParser.INSTANCE);
+        registerPEMResourceParser(PKCS8PEMResourceKeyPairParser.INSTANCE);
+    }
+
+    private PEMResourceParserUtils() {
+        throw new UnsupportedOperationException("No instance");
+    }
+
+    public static void registerPEMResourceParser(KeyPairPEMResourceParser parser) {
+        Objects.requireNonNull(parser, "No parser to register");
+        synchronized (BY_OID_MAP) {
+            BY_OID_MAP.put(ValidateUtils.checkNotNullAndNotEmpty(parser.getAlgorithmIdentifier(), "No OID value"), parser);
+        }
+
+        synchronized (BY_ALGORITHM_MAP) {
+            BY_ALGORITHM_MAP.put(ValidateUtils.checkNotNullAndNotEmpty(parser.getAlgorithm(), "No algorithm value"), parser);
+            // Use a copy in order to avoid concurrent modifications
+            PROXY_HOLDER.set(KeyPairResourceParser.aggregate(new ArrayList<>(BY_ALGORITHM_MAP.values())));
+        }
+    }
+
+    public static KeyPairPEMResourceParser getPEMResourceParserByOid(String oid) {
+        if (GenericUtils.isEmpty(oid)) {
+            return null;
+        }
+
+        synchronized (BY_OID_MAP) {
+            return BY_OID_MAP.get(oid);
+        }
+    }
+
+    public static KeyPairPEMResourceParser getPEMResourceParserByAlgorithm(String algorithm) {
+        if (GenericUtils.isEmpty(algorithm)) {
+            return null;
+        }
+
+        synchronized (BY_ALGORITHM_MAP) {
+            return BY_ALGORITHM_MAP.get(algorithm);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/PKCS8PEMResourceKeyPairParser.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/PKCS8PEMResourceKeyPairParser.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/PKCS8PEMResourceKeyPairParser.java
new file mode 100644
index 0000000..b333f23
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/PKCS8PEMResourceKeyPairParser.java
@@ -0,0 +1,156 @@
+/*
+ * 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.loader.pem;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.StreamCorruptedException;
+import java.security.GeneralSecurityException;
+import java.security.KeyFactory;
+import java.security.KeyPair;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.sshd.common.config.keys.FilePasswordProvider;
+import org.apache.sshd.common.config.keys.KeyUtils;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.ValidateUtils;
+import org.apache.sshd.common.util.io.IoUtils;
+import org.apache.sshd.common.util.io.der.ASN1Object;
+import org.apache.sshd.common.util.io.der.DERParser;
+import org.apache.sshd.common.util.security.SecurityUtils;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class PKCS8PEMResourceKeyPairParser extends AbstractPEMResourceKeyPairParser {
+    // Not exactly according to standard but good enough
+    public static final String BEGIN_MARKER = "BEGIN PRIVATE KEY";
+    public static final List<String> BEGINNERS =
+            Collections.unmodifiableList(Collections.singletonList(BEGIN_MARKER));
+
+    public static final String END_MARKER = "END PRIVATE KEY";
+    public static final List<String> ENDERS =
+            Collections.unmodifiableList(Collections.singletonList(END_MARKER));
+
+    public static final String PKCS8_FORMAT = "PKCS#8";
+
+    public static final PKCS8PEMResourceKeyPairParser INSTANCE = new PKCS8PEMResourceKeyPairParser();
+
+    public PKCS8PEMResourceKeyPairParser() {
+        super(PKCS8_FORMAT, PKCS8_FORMAT, BEGINNERS, ENDERS);
+    }
+
+    @Override
+    public Collection<KeyPair> extractKeyPairs(
+            String resourceKey, String beginMarker, String endMarker, FilePasswordProvider passwordProvider, InputStream stream)
+                    throws IOException, GeneralSecurityException {
+        // Save the data before getting the algorithm OID since we will need it
+        byte[] encBytes = IoUtils.toByteArray(stream);
+        List<Integer> oidAlgorithm = getPKCS8AlgorithmIdentifier(encBytes);
+        PrivateKey prvKey = decodePEMPrivateKeyPKCS8(oidAlgorithm, encBytes, passwordProvider);
+        PublicKey pubKey = ValidateUtils.checkNotNull(KeyUtils.recoverPublicKey(prvKey),
+                "Failed to recover public key of OID=%s", oidAlgorithm);
+        KeyPair kp = new KeyPair(pubKey, prvKey);
+        return Collections.singletonList(kp);
+    }
+
+    public static PrivateKey decodePEMPrivateKeyPKCS8(
+            List<Integer> oidAlgorithm, byte[] keyBytes, FilePasswordProvider passwordProvider)
+                    throws GeneralSecurityException {
+        ValidateUtils.checkNotNullAndNotEmpty(oidAlgorithm, "No PKCS8 algorithm OID");
+        return decodePEMPrivateKeyPKCS8(GenericUtils.join(oidAlgorithm, '.'), keyBytes, passwordProvider);
+    }
+
+    public static PrivateKey decodePEMPrivateKeyPKCS8(
+            String oid, byte[] keyBytes, FilePasswordProvider passwordProvider)
+                    throws GeneralSecurityException {
+        KeyPairPEMResourceParser parser =
+            PEMResourceParserUtils.getPEMResourceParserByOid(
+                ValidateUtils.checkNotNullAndNotEmpty(oid, "No PKCS8 algorithm OID"));
+        if (parser == null) {
+            throw new NoSuchAlgorithmException("decodePEMPrivateKeyPKCS8(" + oid + ") unknown algorithm identifier");
+        }
+
+        String algorithm = ValidateUtils.checkNotNullAndNotEmpty(parser.getAlgorithm(), "No parser algorithm");
+        KeyFactory factory = SecurityUtils.getKeyFactory(algorithm);
+        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
+        return factory.generatePrivate(keySpec);
+    }
+
+    public static List<Integer> getPKCS8AlgorithmIdentifier(byte[] input) throws IOException {
+        try (DERParser parser = new DERParser(input)) {
+            return getPKCS8AlgorithmIdentifier(parser);
+        }
+    }
+
+    /**
+     * According to the standard:
+     * <PRE><CODE>
+     * PrivateKeyInfo ::= SEQUENCE {
+     *          version Version,
+     *          privateKeyAlgorithm PrivateKeyAlgorithmIdentifier,
+     *          privateKey PrivateKey,
+     *          attributes [0] IMPLICIT Attributes OPTIONAL
+     *  }
+     *
+     * Version ::= INTEGER
+     * PrivateKeyAlgorithmIdentifier ::= AlgorithmIdentifier
+     * PrivateKey ::= OCTET STRING
+     * Attributes ::= SET OF Attribute
+     * AlgorithmIdentifier ::= SEQUENCE {
+     *      algorithm       OBJECT IDENTIFIER,
+     *      parameters      ANY DEFINED BY algorithm OPTIONAL
+     * }
+     * </CODE></PRE>
+     * @param parser The {@link DERParser} to use
+     * @return The PKCS8 algorithm OID
+     * @throws IOException If malformed data
+     * @see #getPKCS8AlgorithmIdentifier(ASN1Object)
+     */
+    public static List<Integer> getPKCS8AlgorithmIdentifier(DERParser parser) throws IOException {
+        return getPKCS8AlgorithmIdentifier(parser.readObject());
+    }
+
+    public static List<Integer> getPKCS8AlgorithmIdentifier(ASN1Object privateKeyInfo) throws IOException {
+        try (DERParser parser = privateKeyInfo.createParser()) {
+            // Skip version
+            ASN1Object versionObject = parser.readObject();
+            if (versionObject == null) {
+                throw new StreamCorruptedException("No version");
+            }
+
+            ASN1Object privateKeyAlgorithm = parser.readObject();
+            if (privateKeyAlgorithm == null) {
+                throw new StreamCorruptedException("No private key algorithm");
+            }
+
+            try (DERParser oidParser = privateKeyAlgorithm.createParser()) {
+                ASN1Object oid = oidParser.readObject();
+                return oid.asOID();
+            }
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/RSAPEMResourceKeyPairParser.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/RSAPEMResourceKeyPairParser.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/RSAPEMResourceKeyPairParser.java
new file mode 100644
index 0000000..d760aaf
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/RSAPEMResourceKeyPairParser.java
@@ -0,0 +1,142 @@
+/*
+ * 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.loader.pem;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.StreamCorruptedException;
+import java.math.BigInteger;
+import java.security.GeneralSecurityException;
+import java.security.KeyFactory;
+import java.security.KeyPair;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.spec.RSAPrivateCrtKeySpec;
+import java.security.spec.RSAPrivateKeySpec;
+import java.security.spec.RSAPublicKeySpec;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.sshd.common.config.keys.FilePasswordProvider;
+import org.apache.sshd.common.config.keys.KeyUtils;
+import org.apache.sshd.common.util.io.NoCloseInputStream;
+import org.apache.sshd.common.util.io.der.ASN1Object;
+import org.apache.sshd.common.util.io.der.ASN1Type;
+import org.apache.sshd.common.util.io.der.DERParser;
+import org.apache.sshd.common.util.security.SecurityUtils;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class RSAPEMResourceKeyPairParser extends AbstractPEMResourceKeyPairParser {
+    // Not exactly according to standard but good enough
+    public static final String BEGIN_MARKER = "BEGIN RSA PRIVATE KEY";
+    public static final List<String> BEGINNERS =
+            Collections.unmodifiableList(Collections.singletonList(BEGIN_MARKER));
+
+    public static final String END_MARKER = "END RSA PRIVATE KEY";
+    public static final List<String> ENDERS =
+            Collections.unmodifiableList(Collections.singletonList(END_MARKER));
+
+    /**
+     * @see <A HREF="https://tools.ietf.org/html/rfc3279#section-2.3.1">RFC-3279 section 2.3.1</A>
+     */
+    public static final String RSA_OID = "1.2.840.113549.1.1.1";
+
+    public static final RSAPEMResourceKeyPairParser INSTANCE = new RSAPEMResourceKeyPairParser();
+
+    public RSAPEMResourceKeyPairParser() {
+        super(KeyUtils.RSA_ALGORITHM, RSA_OID, BEGINNERS, ENDERS);
+    }
+
+    @Override
+    public Collection<KeyPair> extractKeyPairs(
+            String resourceKey, String beginMarker, String endMarker, FilePasswordProvider passwordProvider, InputStream stream)
+                    throws IOException, GeneralSecurityException {
+        KeyPair kp = decodeRSAKeyPair(SecurityUtils.getKeyFactory(KeyUtils.RSA_ALGORITHM), stream, false);
+        return Collections.singletonList(kp);
+    }
+
+    /**
+     * <p>The ASN.1 syntax for the private key as per RFC-3447 section A.1.2:</P>
+     * <pre><code>
+     * RSAPrivateKey ::= SEQUENCE {
+     *   version           Version,
+     *   modulus           INTEGER,  -- n
+     *   publicExponent    INTEGER,  -- e
+     *   privateExponent   INTEGER,  -- d
+     *   prime1            INTEGER,  -- p
+     *   prime2            INTEGER,  -- q
+     *   exponent1         INTEGER,  -- d mod (p-1)
+     *   exponent2         INTEGER,  -- d mod (q-1)
+     *   coefficient       INTEGER,  -- (inverse of q) mod p
+     *   otherPrimeInfos   OtherPrimeInfos OPTIONAL
+     * }
+     * </code></pre>
+     * @param kf The {@link KeyFactory} To use to generate the keys
+     * @param s The {@link InputStream} containing the encoded bytes
+     * @param okToClose <code>true</code> if the method may close the input
+     * stream regardless of success or failure
+     * @return The recovered {@link KeyPair}
+     * @throws IOException If failed to read or decode the bytes
+     * @throws GeneralSecurityException If failed to generate the keys
+     */
+    public static KeyPair decodeRSAKeyPair(KeyFactory kf, InputStream s, boolean okToClose)
+            throws IOException, GeneralSecurityException {
+        ASN1Object sequence;
+        try (DERParser parser = new DERParser(NoCloseInputStream.resolveInputStream(s, okToClose))) {
+            sequence = parser.readObject();
+        }
+
+        if (!ASN1Type.SEQUENCE.equals(sequence.getObjType())) {
+            throw new IOException("Invalid DER: not a sequence: " + sequence.getObjType());
+        }
+
+        try (DERParser parser = sequence.createParser()) {
+            // Skip version
+            ASN1Object versionObject = parser.readObject();
+            if (versionObject == null) {
+                throw new StreamCorruptedException("No version");
+            }
+
+            // as per RFC-3447 section A.1.2
+            BigInteger version = versionObject.asInteger();
+            if (!BigInteger.ZERO.equals(version)) {
+                throw new StreamCorruptedException("Multi-primes N/A");
+            }
+
+            BigInteger modulus = parser.readObject().asInteger();
+            BigInteger publicExp = parser.readObject().asInteger();
+            PublicKey pubKey = kf.generatePublic(new RSAPublicKeySpec(modulus, publicExp));
+
+            BigInteger privateExp = parser.readObject().asInteger();
+            BigInteger primeP = parser.readObject().asInteger();
+            BigInteger primeQ = parser.readObject().asInteger();
+            BigInteger primeExponentP = parser.readObject().asInteger();
+            BigInteger primeExponentQ = parser.readObject().asInteger();
+            BigInteger crtCoef = parser.readObject().asInteger();
+            RSAPrivateKeySpec prvSpec = new RSAPrivateCrtKeySpec(
+                    modulus, publicExp, privateExp, primeP, primeQ, primeExponentP, primeExponentQ, crtCoef);
+            PrivateKey prvKey = kf.generatePrivate(prvSpec);
+            return new KeyPair(pubKey, prvKey);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/digest/BaseDigest.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/digest/BaseDigest.java b/sshd-common/src/main/java/org/apache/sshd/common/digest/BaseDigest.java
new file mode 100644
index 0000000..a5ef6f9
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/digest/BaseDigest.java
@@ -0,0 +1,156 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sshd.common.digest;
+
+import java.security.MessageDigest;
+import java.util.Objects;
+
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.NumberUtils;
+import org.apache.sshd.common.util.ValidateUtils;
+import org.apache.sshd.common.util.security.SecurityUtils;
+
+/**
+ * Base class for Digest algorithms based on the JCE provider.
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class BaseDigest implements Digest {
+
+    private final String algorithm;
+    private final int bsize;
+    private int h;
+    private String s;
+    private MessageDigest md;
+
+    /**
+     * Create a new digest using the given algorithm and block size.
+     * The initialization and creation of the underlying {@link MessageDigest}
+     * object will be done in the {@link #init()} method.
+     *
+     * @param algorithm the JCE algorithm to use for this digest
+     * @param bsize     the block size of this digest
+     */
+    public BaseDigest(String algorithm, int bsize) {
+        this.algorithm = ValidateUtils.checkNotNullAndNotEmpty(algorithm, "No algorithm");
+        ValidateUtils.checkTrue(bsize > 0, "Invalid block size: %d", bsize);
+        this.bsize = bsize;
+    }
+
+    @Override
+    public final String getAlgorithm() {
+        return algorithm;
+    }
+
+    @Override
+    public int getBlockSize() {
+        return bsize;
+    }
+
+    @Override
+    public void init() throws Exception {
+        this.md = SecurityUtils.getMessageDigest(getAlgorithm());
+    }
+
+    @Override
+    public void update(byte[] data) throws Exception {
+        update(data, 0, NumberUtils.length(data));
+    }
+
+    @Override
+    public void update(byte[] data, int start, int len) throws Exception {
+        Objects.requireNonNull(md, "Digest not initialized").update(data, start, len);
+    }
+
+    /**
+     * @return The current {@link MessageDigest} - may be {@code null} if {@link #init()} not called
+     */
+    protected MessageDigest getMessageDigest() {
+        return md;
+    }
+
+    @Override
+    public byte[] digest() throws Exception {
+        return Objects.requireNonNull(md, "Digest not initialized").digest();
+    }
+
+    @Override
+    public int hashCode() {
+        synchronized (this) {
+            if (h == 0) {
+                h = Objects.hashCode(getAlgorithm()) + getBlockSize();
+                if (h == 0) {
+                    h = 1;
+                }
+            }
+        }
+
+        return h;
+    }
+
+    @Override
+    public int compareTo(Digest that) {
+        if (that == null) {
+            return -1;    // push null(s) to end
+        } else if (this == that) {
+            return 0;
+        }
+
+        String thisAlg = getAlgorithm();
+        String thatAlg = that.getAlgorithm();
+        int nRes = GenericUtils.safeCompare(thisAlg, thatAlg, false);
+        if (nRes != 0) {
+            return nRes;    // debug breakpoint
+        }
+
+        nRes = Integer.compare(this.getBlockSize(), that.getBlockSize());
+        if (nRes != 0) {
+            return nRes;    // debug breakpoint
+        }
+
+        return 0;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == null) {
+            return false;
+        }
+        if (obj == this) {
+            return true;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+
+        int nRes = compareTo((Digest) obj);
+        return nRes == 0;
+    }
+
+    @Override
+    public String toString() {
+        synchronized (this) {
+            if (s == null) {
+                s = getClass().getSimpleName() + "[" + getAlgorithm() + ":" + getBlockSize() + "]";
+            }
+        }
+
+        return s;
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/digest/BuiltinDigests.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/digest/BuiltinDigests.java b/sshd-common/src/main/java/org/apache/sshd/common/digest/BuiltinDigests.java
new file mode 100644
index 0000000..f469583
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/digest/BuiltinDigests.java
@@ -0,0 +1,166 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sshd.common.digest;
+
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.Set;
+
+import org.apache.sshd.common.NamedFactory;
+import org.apache.sshd.common.NamedResource;
+import org.apache.sshd.common.util.GenericUtils;
+
+/**
+ * Provides easy access to the currently implemented digests
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public enum BuiltinDigests implements DigestFactory {
+    md5(Constants.MD5, "MD5", 16),
+    sha1(Constants.SHA1, "SHA-1", 20),
+    sha224(Constants.SHA224, "SHA-224", 28),
+    sha256(Constants.SHA256, "SHA-256", 32),
+    sha384(Constants.SHA384, "SHA-384", 48),
+    sha512(Constants.SHA512, "SHA-512", 64);
+
+    public static final Set<BuiltinDigests> VALUES =
+            Collections.unmodifiableSet(EnumSet.allOf(BuiltinDigests.class));
+
+    private final String algorithm;
+    private final int blockSize;
+    private final String factoryName;
+    private final boolean supported;
+
+    BuiltinDigests(String factoryName, String algorithm, int blockSize) {
+        this.factoryName = factoryName;
+        this.algorithm = algorithm;
+        this.blockSize = blockSize;
+        /*
+         * This can be done once since in order to change the support the JVM
+         * needs to be stopped, some unlimited-strength files need be installed
+         * and then the JVM re-started. Therefore, the answer is not going to
+         * change while the JVM is running
+         */
+        this.supported = DigestUtils.checkSupported(algorithm);
+    }
+
+    @Override
+    public final String getName() {
+        return factoryName;
+    }
+
+    @Override
+    public final String getAlgorithm() {
+        return algorithm;
+    }
+
+    @Override
+    public final int getBlockSize() {
+        return blockSize;
+    }
+
+    @Override
+    public final String toString() {
+        return getName();
+    }
+
+    @Override
+    public final Digest create() {
+        return new BaseDigest(getAlgorithm(), getBlockSize());
+    }
+
+    @Override
+    public final boolean isSupported() {
+        return supported;
+    }
+
+    /**
+     * @param s The {@link Enum}'s name - ignored if {@code null}/empty
+     * @return The matching {@link org.apache.sshd.common.digest.BuiltinDigests} whose {@link Enum#name()} matches
+     * (case <U>insensitive</U>) the provided argument - {@code null} if no match
+     */
+    public static BuiltinDigests fromString(String s) {
+        if (GenericUtils.isEmpty(s)) {
+            return null;
+        }
+
+        for (BuiltinDigests c : VALUES) {
+            if (s.equalsIgnoreCase(c.name())) {
+                return c;
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * @param factory The {@link org.apache.sshd.common.NamedFactory} for the cipher - ignored if {@code null}
+     * @return The matching {@link org.apache.sshd.common.digest.BuiltinDigests} whose factory name matches
+     * (case <U>insensitive</U>) the digest factory name
+     * @see #fromFactoryName(String)
+     */
+    public static BuiltinDigests fromFactory(NamedFactory<? extends Digest> factory) {
+        if (factory == null) {
+            return null;
+        } else {
+            return fromFactoryName(factory.getName());
+        }
+    }
+
+    /**
+     * @param name The factory name - ignored if {@code null}/empty
+     * @return The matching {@link org.apache.sshd.common.digest.BuiltinDigests} whose factory name matches
+     * (case <U>insensitive</U>) the provided name - {@code null} if no match
+     */
+    public static BuiltinDigests fromFactoryName(String name) {
+        return NamedResource.findByName(name, String.CASE_INSENSITIVE_ORDER, VALUES);
+    }
+
+    /**
+     * @param d The {@link Digest} instance - ignored if {@code null}
+     * @return The matching {@link org.apache.sshd.common.digest.BuiltinDigests} whose algorithm matches
+     * (case <U>insensitive</U>) the digets's algorithm - {@code null} if no match
+     */
+    public static BuiltinDigests fromDigest(Digest d) {
+        return fromAlgorithm((d == null) ? null : d.getAlgorithm());
+    }
+
+    /**
+     * @param algo The algorithm to find - ignored if {@code null}/empty
+     * @return The matching {@link org.apache.sshd.common.digest.BuiltinDigests} whose algorithm matches
+     * (case <U>insensitive</U>) the provided name - {@code null} if no match
+     */
+    public static BuiltinDigests fromAlgorithm(String algo) {
+        return DigestUtils.findFactoryByAlgorithm(algo, String.CASE_INSENSITIVE_ORDER, VALUES);
+    }
+
+    public static final class Constants {
+        public static final String MD5 = "md5";
+        public static final String SHA1 = "sha1";
+        public static final String SHA224 = "sha224";
+        public static final String SHA256 = "sha256";
+        public static final String SHA384 = "sha384";
+        public static final String SHA512 = "sha512";
+
+        private Constants() {
+            throw new UnsupportedOperationException("No instance allowed");
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/digest/Digest.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/digest/Digest.java b/sshd-common/src/main/java/org/apache/sshd/common/digest/Digest.java
new file mode 100644
index 0000000..27204ff
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/digest/Digest.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sshd.common.digest;
+
+/**
+ * Interface used to compute digests, based on algorithms such as MD5 or SHA1.
+ * The digest implementation are compared first by the algorithm name (case
+ * <U>insensitive</U> and second according to the block size
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public interface Digest extends DigestInformation, Comparable<Digest> {
+    void init() throws Exception;
+
+    void update(byte[] data) throws Exception;
+
+    void update(byte[] data, int start, int len) throws Exception;
+
+    byte[] digest() throws Exception;
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/digest/DigestFactory.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/digest/DigestFactory.java b/sshd-common/src/main/java/org/apache/sshd/common/digest/DigestFactory.java
new file mode 100644
index 0000000..f00e7ba
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/digest/DigestFactory.java
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sshd.common.digest;
+
+import org.apache.sshd.common.NamedFactory;
+import org.apache.sshd.common.OptionalFeature;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+// CHECKSTYLE:OFF
+public interface DigestFactory extends DigestInformation, NamedFactory<Digest>, OptionalFeature {
+    // nothing extra
+}
+// CHECKSTYLE:ON

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/digest/DigestInformation.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/digest/DigestInformation.java b/sshd-common/src/main/java/org/apache/sshd/common/digest/DigestInformation.java
new file mode 100644
index 0000000..ba181dc
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/digest/DigestInformation.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sshd.common.digest;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public interface DigestInformation {
+    /**
+     * @return The digest algorithm name
+     */
+    String getAlgorithm();
+
+    /**
+     * @return The number of bytes in the digest's output
+     */
+    int getBlockSize();
+
+}