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 2020/05/22 16:46:37 UTC

[mina-sshd] 02/02: [SSHD-989] Fixed parsing of PKCS8 encoded PEM private key files

This is an automated email from the ASF dual-hosted git repository.

lgoldstein pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/mina-sshd.git

commit d14a99bb5504198111cbe5514063747a98e8d2f5
Author: Lyor Goldstein <lg...@apache.org>
AuthorDate: Fri May 22 16:08:28 2020 +0300

    [SSHD-989] Fixed parsing of PKCS8 encoded PEM private key files
---
 CHANGES.md                                         |   2 +-
 .../loader/pem/ECDSAPEMResourceKeyPairParser.java  | 113 ++++++++++++++-------
 .../loader/pem/PKCS8PEMResourceKeyPairParser.java  |  15 ++-
 .../keys/loader/pem/PKCS8PrivateKeyInfo.java       |  39 ++++++-
 .../apache/sshd/common/util/io/der/DERParser.java  |   6 ++
 .../sshd/common/config/keys/KeyUtilsCloneTest.java |   1 -
 .../pem/PKCS8PEMResourceKeyPairParserTest.java     |  40 ++++----
 .../common/config/keys/loader/pem/pkcs8-ec-256.pem |   5 +
 .../common/config/keys/loader/pem/pkcs8-ec-384.pem |   6 ++
 .../common/config/keys/loader/pem/pkcs8-ec-521.pem |   8 ++
 .../config/keys/loader/pem/pkcs8-rsa-1024.pem      |  16 +++
 .../config/keys/loader/pem/pkcs8-rsa-2048.pem      |  28 +++++
 .../config/keys/loader/pem/pkcs8-rsa-3072.pem      |  40 ++++++++
 .../config/keys/loader/pem/pkcs8-rsa-4096.pem      |  52 ++++++++++
 14 files changed, 307 insertions(+), 64 deletions(-)

diff --git a/CHANGES.md b/CHANGES.md
index 1495950..001985f 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -57,4 +57,4 @@ as much of the available functionality as possible.
 
 * [SSHD-998](https://issues.apache.org/jira/browse/SSHD-998) - Take into account SFTP version preference when establishing initial channel
 
-* [SSHD-989](https://issues.apache.org/jira/browse/SSHD-989) - Implement ECDSA public key recovery for PKCS8 encoded data
+* [SSHD-989](https://issues.apache.org/jira/browse/SSHD-989) - Read correctly ECDSA key pair from PKCS8 encoded data
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
index 0fe9b15..08fe839 100644
--- 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
@@ -87,14 +87,23 @@ public class ECDSAPEMResourceKeyPairParser extends AbstractPEMResourceKeyPairPar
             InputStream inputStream, boolean okToClose)
             throws IOException, GeneralSecurityException {
         try (DERParser parser = new DERParser(NoCloseInputStream.resolveInputStream(inputStream, okToClose))) {
-            return parseECKeyPair(parser);
+            return parseECKeyPair(null, parser);
         }
     }
 
-    public static KeyPair parseECKeyPair(DERParser parser)
+    /**
+     * @param  curve                    The {@link ECCurves curve} represented by this data (in case it was optional and
+     *                                  somehow known externally) if {@code null} then it is assumed to be part of the
+     *                                  parsed data. then it is assumed to be part of the data.
+     * @param  parser                   The {@link DERParser} for the data
+     * @return                          The parsed {@link KeyPair}
+     * @throws IOException              If failed to parse the data
+     * @throws GeneralSecurityException If failed to generate the keys
+     */
+    public static KeyPair parseECKeyPair(ECCurves curve, DERParser parser)
             throws IOException, GeneralSecurityException {
         ASN1Object sequence = parser.readObject();
-        Map.Entry<ECPublicKeySpec, ECPrivateKeySpec> spec = decodeECPrivateKeySpec(sequence);
+        Map.Entry<ECPublicKeySpec, ECPrivateKeySpec> spec = decodeECPrivateKeySpec(curve, sequence);
         if (!SecurityUtils.isECCSupported()) {
             throw new NoSuchProviderException("ECC not supported");
         }
@@ -136,12 +145,14 @@ public class ECDSAPEMResourceKeyPairParser extends AbstractPEMResourceKeyPairPar
      * </CODE>
      * </PRE>
      *
+     * @param  curve       The {@link ECCurves curve} represented by this data (in case it was optional and somehow
+     *                     known externally) if {@code null} then it is assumed to be part of the parsed data.
      * @param  sequence    The {@link ASN1Object} sequence containing the DER encoded data
      * @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(ASN1Object sequence)
+    public static Map.Entry<ECPublicKeySpec, ECPrivateKeySpec> decodeECPrivateKeySpec(ECCurves curve, ASN1Object sequence)
             throws IOException {
         ASN1Type objType = (sequence == null) ? null : sequence.getObjType();
         if (!ASN1Type.SEQUENCE.equals(objType)) {
@@ -149,13 +160,10 @@ public class ECDSAPEMResourceKeyPairParser extends AbstractPEMResourceKeyPairPar
         }
 
         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);
+            Map.Entry<ECPrivateKeySpec, ASN1Object> result = decodeECPrivateKeySpec(curve, parser);
+            ECPrivateKeySpec prvSpec = result.getKey();
+            ASN1Object publicData = result.getValue();
+            ECPoint w = (publicData == null) ? decodeECPublicKeyValue(parser) : decodeECPointData(publicData);
             ECPublicKeySpec pubSpec = new ECPublicKeySpec(w, prvSpec.getParams());
             return new SimpleImmutableEntry<>(pubSpec, prvSpec);
         }
@@ -171,7 +179,8 @@ public class ECDSAPEMResourceKeyPairParser extends AbstractPEMResourceKeyPairPar
      *      publicKey [1] BIT STRING OPTIONAL
      * }
      */
-    public static final ECPrivateKeySpec decodeECPrivateKeySpec(DERParser parser) throws IOException {
+    public static Map.Entry<ECPrivateKeySpec, ASN1Object> decodeECPrivateKeySpec(ECCurves curve, DERParser parser)
+            throws IOException {
         // see openssl asn1parse -inform PEM -in ...file... -dump
         ASN1Object versionObject = parser.readObject();
         if (versionObject == null) {
@@ -208,12 +217,36 @@ public class ECDSAPEMResourceKeyPairParser extends AbstractPEMResourceKeyPairPar
          * curve domain parameters. Though the ASN.1 indicates that the parameters field is OPTIONAL, implementations
          * that conform to this document MUST always include the parameters field.
          */
-        ASN1Object paramsObject = parser.readObject();
+        Map.Entry<ECCurves, ASN1Object> result = parseCurveParameter(parser);
+        ECCurves namedParam = (result == null) ? null : result.getKey();
+        if (namedParam == null) {
+            if (curve == null) {
+                throw new StreamCorruptedException("Cannot determine curve type");
+            }
+        } else if (curve == null) {
+            curve = namedParam;
+        } else if (namedParam != curve) {
+            throw new StreamCorruptedException("Mismatched provide (" + curve + ") vs. parsed curve (" + namedParam + ")");
+        }
+
+        BigInteger s = ECCurves.octetStringToInteger(keyObject.getPureValueBytes());
+        ECPrivateKeySpec keySpec = new ECPrivateKeySpec(s, curve.getParameters());
+        return new SimpleImmutableEntry<>(keySpec, (result == null) ? null : result.getValue());
+    }
+
+    public static Map.Entry<ECCurves, ASN1Object> parseCurveParameter(DERParser parser) throws IOException {
+        return parseCurveParameter(parser.readObject());
+    }
+
+    public static Map.Entry<ECCurves, ASN1Object> parseCurveParameter(ASN1Object paramsObject) throws IOException {
         if (paramsObject == null) {
-            throw new StreamCorruptedException("No parameters value");
+            return null;
         }
 
-        // TODO make sure params object tag is 0xA0
+        ASN1Type objType = paramsObject.getObjType();
+        if (objType == ASN1Type.NULL) {
+            return null;
+        }
 
         List<Integer> curveOID;
         try (DERParser paramsParser = paramsObject.createParser()) {
@@ -223,10 +256,13 @@ public class ECDSAPEMResourceKeyPairParser extends AbstractPEMResourceKeyPairPar
             }
 
             /*
-             * SSHD-989 - if object type is BIT STRING then this is the public
-             * key - in which case we need to figure out some other way to recover
-             * the curve parameters
+             * The curve OID is OPTIONAL - if it is not there then the
+             * public key data replaces it
              */
+            objType = namedCurve.getObjType();
+            if (objType == ASN1Type.BIT_STRING) {
+                return new SimpleImmutableEntry<>(null, namedCurve);
+            }
 
             curveOID = namedCurve.asOID();
         }
@@ -236,8 +272,7 @@ public class ECDSAPEMResourceKeyPairParser extends AbstractPEMResourceKeyPairPar
             throw new StreamCorruptedException("Unknown curve OID: " + curveOID);
         }
 
-        BigInteger s = ECCurves.octetStringToInteger(keyObject.getPureValueBytes());
-        return new ECPrivateKeySpec(s, curve.getParameters());
+        return new SimpleImmutableEntry<>(curve, null);
     }
 
     /**
@@ -252,14 +287,16 @@ public class ECDSAPEMResourceKeyPairParser extends AbstractPEMResourceKeyPairPar
      * </code>
      * </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 {
+    public static final ECPoint decodeECPublicKeyValue(DERParser parser) throws IOException {
+        return decodeECPublicKeyValue(parser.readObject());
+    }
+
+    public static final ECPoint decodeECPublicKeyValue(ASN1Object dataObject) 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");
         }
@@ -267,24 +304,26 @@ public class ECDSAPEMResourceKeyPairParser extends AbstractPEMResourceKeyPairPar
         /*
          * According to https://tools.ietf.org/html/rfc5915
          *
-         * Though the ASN.1 indicates publicKey is OPTIONAL, implementations that conform to this document SHOULD always
-         * include the publicKey field
+         * Though the ASN.1 indicates publicKey is OPTIONAL, implementations
+         * that conform to this document SHOULD always include the publicKey field
          */
         try (DERParser dataParser = dataObject.createParser()) {
-            ASN1Object pointData = dataParser.readObject();
-            if (pointData == null) {
-                throw new StreamCorruptedException("Missing public key data parameter");
-            }
+            return decodeECPointData(dataParser.readObject());
+        }
+    }
 
-            ASN1Type objType = pointData.getObjType();
-            if (!ASN1Type.BIT_STRING.equals(objType)) {
-                throw new StreamCorruptedException("Non-matching public key object type: " + objType);
-            }
+    public static final ECPoint decodeECPointData(ASN1Object pointData) throws IOException {
+        if (pointData == null) {
+            throw new StreamCorruptedException("Missing public key data parameter");
+        }
 
-            // see https://tools.ietf.org/html/rfc5480#section-2.2
-            byte[] octets = pointData.getValue();
-            return ECCurves.octetStringToEcPoint(octets);
+        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);
+    }
 }
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
index f871a3a..3207c38 100644
--- 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
@@ -34,6 +34,7 @@ import java.util.List;
 import java.util.Map;
 
 import org.apache.sshd.common.NamedResource;
+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.session.SessionContext;
@@ -41,6 +42,7 @@ 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.ASN1Type;
 import org.apache.sshd.common.util.io.der.DERParser;
 import org.apache.sshd.common.util.security.SecurityUtils;
 
@@ -91,8 +93,19 @@ public class PKCS8PEMResourceKeyPairParser extends AbstractPEMResourceKeyPairPar
         if (SecurityUtils.isECCSupported()
                 && ECDSAPEMResourceKeyPairParser.ECDSA_OID.equals(oid)) {
             ASN1Object privateKeyBytes = pkcs8Info.getPrivateKeyBytes();
+            ASN1Object extraInfo = pkcs8Info.getAlgorithmParameter();
+            ASN1Type objType = (extraInfo == null) ? ASN1Type.NULL : extraInfo.getObjType();
+            List<Integer> oidCurve = (objType == ASN1Type.NULL) ? Collections.emptyList() : extraInfo.asOID();
+            ECCurves curve = null;
+            if (GenericUtils.isNotEmpty(oidCurve)) {
+                curve = ECCurves.fromOIDValue(oidCurve);
+                if (curve == null) {
+                    throw new NoSuchAlgorithmException("Cannot match EC curve OID=" + oidCurve);
+                }
+            }
+
             try (DERParser parser = privateKeyBytes.createParser()) {
-                kp = ECDSAPEMResourceKeyPairParser.parseECKeyPair(parser);
+                kp = ECDSAPEMResourceKeyPairParser.parseECKeyPair(curve, parser);
             }
         } else {
             PrivateKey prvKey = decodePEMPrivateKeyPKCS8(oidAlgorithm, encBytes);
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/PKCS8PrivateKeyInfo.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/PKCS8PrivateKeyInfo.java
index 48917a5..b8732b5 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/PKCS8PrivateKeyInfo.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/PKCS8PrivateKeyInfo.java
@@ -55,6 +55,7 @@ import org.apache.sshd.common.util.io.der.DERParser;
 public class PKCS8PrivateKeyInfo /* TODO Cloneable */ {
     private BigInteger version;
     private List<Integer> algorithmIdentifier;
+    private ASN1Object algorithmParameter;
     private ASN1Object privateKeyBytes;
 
     public PKCS8PrivateKeyInfo() {
@@ -89,6 +90,14 @@ public class PKCS8PrivateKeyInfo /* TODO Cloneable */ {
         this.algorithmIdentifier = algorithmIdentifier;
     }
 
+    public ASN1Object getAlgorithmParameter() {
+        return algorithmParameter;
+    }
+
+    public void setAlgorithmParameter(ASN1Object algorithmParameter) {
+        this.algorithmParameter = algorithmParameter;
+    }
+
     public ASN1Object getPrivateKeyBytes() {
         return privateKeyBytes;
     }
@@ -115,6 +124,21 @@ public class PKCS8PrivateKeyInfo /* TODO Cloneable */ {
      * @throws IOException    If failed to parse the encoding
      */
     public void decode(ASN1Object privateKeyInfo) throws IOException {
+        /*
+         * SEQUENCE {
+         *      INTEGER 0x00 (0 decimal)
+         *      SEQUENCE {
+         *         OBJECTIDENTIFIER encryption type
+         *         OBJECTIDENTIFIER extra info - may be NULL
+         *      }
+         *      OCTETSTRING private key
+         * }
+         */
+        ASN1Type objType = privateKeyInfo.getObjType();
+        if (objType != ASN1Type.SEQUENCE) {
+            throw new StreamCorruptedException("Not a top level sequence: " + objType);
+        }
+
         try (DERParser parser = privateKeyInfo.createParser()) {
             ASN1Object versionObject = parser.readObject();
             if (versionObject == null) {
@@ -128,10 +152,21 @@ public class PKCS8PrivateKeyInfo /* TODO Cloneable */ {
                 throw new StreamCorruptedException("No private key algorithm");
             }
 
+            objType = privateKeyInfo.getObjType();
+            if (objType != ASN1Type.SEQUENCE) {
+                throw new StreamCorruptedException("Not an algorithm parameters sequence: " + objType);
+            }
+
             try (DERParser oidParser = privateKeyAlgorithm.createParser()) {
                 ASN1Object oid = oidParser.readObject();
                 setAlgorithmIdentifier(oid.asOID());
-                // TODO add optional algorithm identifier parameters parsing
+
+                // Extra information is OPTIONAL
+                ASN1Object extraInfo = oidParser.readObject();
+                objType = (extraInfo == null) ? ASN1Type.NULL : extraInfo.getObjType();
+                if (objType != ASN1Type.NULL) {
+                    setAlgorithmParameter(extraInfo);
+                }
             }
 
             ASN1Object privateKeyData = parser.readObject();
@@ -139,7 +174,7 @@ public class PKCS8PrivateKeyInfo /* TODO Cloneable */ {
                 throw new StreamCorruptedException("No private key data");
             }
 
-            ASN1Type objType = privateKeyData.getObjType();
+            objType = privateKeyData.getObjType();
             if (objType != ASN1Type.OCTET_STRING) {
                 throw new StreamCorruptedException("Private key data not an " + ASN1Type.OCTET_STRING + ": " + objType);
             }
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/io/der/DERParser.java b/sshd-common/src/main/java/org/apache/sshd/common/util/io/der/DERParser.java
index 4a4536e..a9fde49 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/util/io/der/DERParser.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/io/der/DERParser.java
@@ -27,6 +27,7 @@ import java.io.StreamCorruptedException;
 import java.math.BigInteger;
 import java.util.Arrays;
 
+import org.apache.sshd.common.util.GenericUtils;
 import org.apache.sshd.common.util.NumberUtils;
 import org.apache.sshd.common.util.buffer.BufferUtils;
 
@@ -122,6 +123,11 @@ public class DERParser extends FilterInputStream {
             return null;
         }
 
+        ASN1Type objType = ASN1Type.fromDERValue(tag);
+        if (objType == ASN1Type.NULL) {
+            return new ASN1Object((byte) tag, 0, GenericUtils.EMPTY_BYTE_ARRAY);
+        }
+
         int length = readLength();
         byte[] value = new byte[length];
         int n = read(value);
diff --git a/sshd-common/src/test/java/org/apache/sshd/common/config/keys/KeyUtilsCloneTest.java b/sshd-common/src/test/java/org/apache/sshd/common/config/keys/KeyUtilsCloneTest.java
index 0b64d08..5b3e9e6 100644
--- a/sshd-common/src/test/java/org/apache/sshd/common/config/keys/KeyUtilsCloneTest.java
+++ b/sshd-common/src/test/java/org/apache/sshd/common/config/keys/KeyUtilsCloneTest.java
@@ -116,5 +116,4 @@ public class KeyUtilsCloneTest extends JUnitTestSupport {
             assertTrue(prefix + ": Cloned private key not equals", KeyUtils.compareKeys(k1, k2));
         }
     }
-
 }
diff --git a/sshd-common/src/test/java/org/apache/sshd/common/config/keys/loader/pem/PKCS8PEMResourceKeyPairParserTest.java b/sshd-common/src/test/java/org/apache/sshd/common/config/keys/loader/pem/PKCS8PEMResourceKeyPairParserTest.java
index 6d62616..76b9224 100644
--- a/sshd-common/src/test/java/org/apache/sshd/common/config/keys/loader/pem/PKCS8PEMResourceKeyPairParserTest.java
+++ b/sshd-common/src/test/java/org/apache/sshd/common/config/keys/loader/pem/PKCS8PEMResourceKeyPairParserTest.java
@@ -26,7 +26,6 @@ import java.security.GeneralSecurityException;
 import java.security.KeyPair;
 import java.security.KeyPairGenerator;
 import java.security.PrivateKey;
-import java.security.PublicKey;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
@@ -43,7 +42,6 @@ import org.apache.sshd.util.test.JUnitTestSupport;
 import org.apache.sshd.util.test.NoIoTestCase;
 import org.junit.Assume;
 import org.junit.FixMethodOrder;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.experimental.categories.Category;
 import org.junit.runner.RunWith;
@@ -81,6 +79,11 @@ public class PKCS8PEMResourceKeyPairParserTest extends JUnitTestSupport {
         }
         if (SecurityUtils.isECCSupported()) {
             for (ECCurves curve : ECCurves.VALUES) {
+                if (!curve.isSupported()) {
+                    outputDebugMessage("Skip unsupported curve=%s", curve);
+                    continue;
+                }
+
                 params.add(new Object[] { KeyUtils.EC_ALGORITHM, curve.getKeySize() });
             }
         }
@@ -88,7 +91,7 @@ public class PKCS8PEMResourceKeyPairParserTest extends JUnitTestSupport {
     }
 
     @Test // see SSHD-760
-    public void testPkcs8() throws IOException, GeneralSecurityException {
+    public void testLocallyGeneratedPkcs8() throws IOException, GeneralSecurityException {
         KeyPairGenerator generator = SecurityUtils.getKeyPairGenerator(algorithm);
         if (keySize > 0) {
             generator.initialize(keySize);
@@ -114,14 +117,18 @@ public class PKCS8PEMResourceKeyPairParserTest extends JUnitTestSupport {
         }
     }
 
-    // see https://gist.github.com/briansmith/2ee42439923d8e65a266994d0f70180b
-    // openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 -pkeyopt ec_param_enc:named_curve -out pkcs8-ecdsa-256.pem
-    // openssl ecparam -genkey -name prime256v1 -noout -out pkcs8-ec-256.key
-    // openssl pkcs8 -topk8 -inform PEM -outform PEM -nocrypt -in pkcs8-ec-256.key -out pkcs8-ec-256.pem
-    // openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:1024 -out pkcs8-rsa-1024.pem
-    // openssl asn1parse -inform PEM -in ...file... -dump
+    /*
+     * See https://gist.github.com/briansmith/2ee42439923d8e65a266994d0f70180b
+     *
+     * openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:1024 -out pkcs8-rsa-1024.pem
+     * openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 -pkeyopt ec_param_enc:named_curve -out pkcs8-ecdsa-256.pem
+     *
+     * openssl ecparam -genkey -name prime256v1 -noout -out pkcs8-ec-256.key
+     * openssl pkcs8 -topk8 -inform PEM -outform PEM -nocrypt -in pkcs8-ec-256.key -out pkcs8-ec-256.pem
+     *
+     * openssl asn1parse -inform PEM -in ...file... -dump
+     */
     @Test // see SSHD-989
-    @Ignore("WIP")
     public void testPKCS8FileParsing() throws Exception {
         String resourceKey = "pkcs8-" + algorithm.toLowerCase() + "-" + keySize + ".pem";
         URL url = getClass().getResource(resourceKey);
@@ -129,18 +136,7 @@ public class PKCS8PEMResourceKeyPairParserTest extends JUnitTestSupport {
 
         Collection<KeyPair> pairs = PKCS8PEMResourceKeyPairParser.INSTANCE.loadKeyPairs(null, url, null);
         assertEquals("Mismatched extract keys count", 1, GenericUtils.size(pairs));
-
-        assertSignatureMatch("Cannot sign with recovered key pair", GenericUtils.head(pairs));
-    }
-
-    private static void assertSignatureMatch(String message, KeyPair kp) throws GeneralSecurityException {
-        assertSignatureMatch(message, kp.getPrivate(), kp.getPublic());
-    }
-
-    private static void assertSignatureMatch(
-            String message, PrivateKey privateKey, PublicKey publicKey)
-            throws GeneralSecurityException {
-        // TODO
+        validateKeyPairSignable(algorithm + "/" + keySize, GenericUtils.head(pairs));
     }
 
     @Override
diff --git a/sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/pem/pkcs8-ec-256.pem b/sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/pem/pkcs8-ec-256.pem
new file mode 100644
index 0000000..d7727e1
--- /dev/null
+++ b/sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/pem/pkcs8-ec-256.pem
@@ -0,0 +1,5 @@
+-----BEGIN PRIVATE KEY-----
+MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgs6QE06EskVFA/o7i
+HrNnF9In14QcJC9EgkXsVFk+SWWhRANCAAT8lGjPLQVdmwglhBP9refqp9Mrr7AN
+pGSOy3cCDtG4JeRr25s+EXavossaZ9U8MWe39wWUV7yvz5BT5hA3HSig
+-----END PRIVATE KEY-----
diff --git a/sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/pem/pkcs8-ec-384.pem b/sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/pem/pkcs8-ec-384.pem
new file mode 100644
index 0000000..e552dfa
--- /dev/null
+++ b/sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/pem/pkcs8-ec-384.pem
@@ -0,0 +1,6 @@
+-----BEGIN PRIVATE KEY-----
+MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDAKagn5+JY4/pjeHLUX
+IORdDtAd8l//84hnzxiWR80AHLnyI8N4YUp7zPGUY0n5/VehZANiAAQacLGO21zt
+XkO/jijS+1BMxfuZyvtDE0fyENi6FNsYz92s+szssUxLl1XPO1Bv7+xdX/nkqjbi
+V26a3G8VzoNUl8KNrYUfH+fcukhVKCU3A9VP8u1HZBhOIn+ouKUSj5E=
+-----END PRIVATE KEY-----
diff --git a/sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/pem/pkcs8-ec-521.pem b/sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/pem/pkcs8-ec-521.pem
new file mode 100644
index 0000000..4281d87
--- /dev/null
+++ b/sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/pem/pkcs8-ec-521.pem
@@ -0,0 +1,8 @@
+-----BEGIN PRIVATE KEY-----
+MIHuAgEAMBAGByqGSM49AgEGBSuBBAAjBIHWMIHTAgEBBEIAliKmTYJEBwcWV4a+
+nbS69ht7d3mvUrp60m7T+gAXxUpb5XNWwaxa3PxRY9Mm4Or8mOfPa8d6rSlNARFP
+mU/zOFuhgYkDgYYABAAmm+nrn29TxAonRop25S9DFRX30ci2E+b3qDBy94N+A06n
+Q+wLo+vK95KbG461R9JUXBlH2qLQnLhUle5KpNw9nwAy5vZgrwqmCB1cdDarkTGr
+FpkVsrovB6mD7nxY/13wws1Ll1or3Bsb6ZQfnZ9VloaEVnnc0QLeO8HaRxif865D
+tA==
+-----END PRIVATE KEY-----
diff --git a/sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/pem/pkcs8-rsa-1024.pem b/sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/pem/pkcs8-rsa-1024.pem
new file mode 100644
index 0000000..6f6df20
--- /dev/null
+++ b/sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/pem/pkcs8-rsa-1024.pem
@@ -0,0 +1,16 @@
+-----BEGIN PRIVATE KEY-----
+MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBALpizZejDUx5l83q
+I9Kg75KhYvj5mRVBdTRS8aN/bjiDNOn+yXeRVIB5Vw/r+V6N+1UzTcD+t4sC1b61
+mFgOqn5d9adDWw//zROovmCujqvJCDWTZPpuj/05u2BzlDzrkgTXQ/k4Owu9SjQQ
+hcrbkaHtvc1/Cfi1DfEv+n7FxABRAgMBAAECgYB9t9ku99cnhziittSU5OLTh7IH
+d+wOz0ksEupUOsbwrWeKkcX4tXlG8xGLdsKMSb6GWIWQsP7CcBYWfcyVUMckL4HP
+0jx+6hx7Y3uFtM1/MkucM0D+UAB6cQXHRr2xoRqpiQWBcOAL7dT7IBRMaB4iOGyI
+sDnsrucoX2hQU6kXQQJBAO8M4+XZgH2t/4HHA3Upmi5j0L7JmWusnq5Lhf8Va8mO
+fLIO0AuKuUtvhWlne3cyUZbaSBOE13DTwNn57LcGjikCQQDHmfpOXDrJCcOjKjQ5
+9g6oVWwU9SgZ51J3lVr83GaMCBdx0zMz5V7eGRsXudAHk6iVHdrznZBEdzWHAlmX
+8VXpAkEA1TqwRiQ+wtxj3wUABpA3YU3Ts3rsCOmPGXVwbtpSrRUWEVW5KbJyGeG+
+JQkTTn1p3Z+TTyXdblzT1xthlNiaEQJABHlaF/mPQ8RZQ0YF56qxR2qqwomAPZxm
+x9FsObDDB66CwAVo52fjyXysk8qRdCoGJFmH99/3ROGbLIyL75D0SQJBAI/EHfWS
+n3+A/S0R+rQ9GIKXa1Y5wEgVPYKk+YPLfxdOUEj5ZLI5jrE2mHn7ILFnyZpgci6l
+wxLeAa68VX4lp7g=
+-----END PRIVATE KEY-----
diff --git a/sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/pem/pkcs8-rsa-2048.pem b/sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/pem/pkcs8-rsa-2048.pem
new file mode 100644
index 0000000..4942fb1
--- /dev/null
+++ b/sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/pem/pkcs8-rsa-2048.pem
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDJqeUR4mteSX3c
+UuZdaYOwdVcck2X9Ce02wxrUM/xa3NokhjQ+tNJY9FU8/fEYRASHMtUm3lBtnJkV
+XWHlA2BKl8oUbkFzfDCk3B1iXGjAZTVkSzImmhhqtgWcKZmGoL5Irf+8w4jHf8rQ
+5mp9hK3nQkTiAlELhAP2cEdDQcmynxN7zFkY0sMZQUeEQO1hHo+plwn3H33Cxq87
+XiyFKAJArqPmOEVkl/6oUY4MX5DRCPaYXGnINsAFY0PfmSPk1iV8HlevaeeiOmx2
+MNKiOw+PtlCr97QymmcdxKJzwmVKTLXrK9NHC8A/SJD6WUdY1aF1mjSVqeo/1y3f
+GfUF9MKZAgMBAAECggEBAKFhon1DcqTLrzsH5G5QqCAoZwPpOS7cKMcwL2IuD/8u
+yit8cobT8ZlaPnRGzA+dLvp6xXULZ9WwAhnE1ziMERzgh8j9ysb+VXc45xL13KZK
+2AVg38tgebW74JVt/Pxt2pkTFZsb53OvYsD2A7Za3Ug6EiHDtNPAW+N1SrIaDa0w
+kB+VMVVnaua+OuNBkrXEbhB14CJG/DGNiMvm6PkVNd9oQkbxykp1c6PU2ZQ4jJos
+6oKeqMwXSSvwWhen9YoYtSaff2bJRy6HWaPiRAHKFw1WQMoAksmAvBsU/nwA6c/t
+AOpzKcvX8b/VFlZpY9vdylXS/2ysK5lDTmXX9FWjhRkCgYEA73V7fnXJ10avbd85
+2/oTFmTB4rOg4a5VBqSy/AZiOwlLUyadUkyLR8gF7vsVB7PSnwH5HlGRORC+COnH
+GVj+44gq3EbjwucqeYlSizW8hjLFOpYcm0AP1naQCiiBG0xUxFwc8xyE0zu+V9KX
+8uHQTGejMEnz7Nx7j+LWxJchsEcCgYEA15gNeCaPb6TMR7LtUxtZ5+Rt0zLdG44Q
+yMmrReT79via7HkFyxOZaO+8tk6APBNGx1mU8IXYpZWK0CPIHeKlqI8roqlfWQdp
+XzQ7Zz1A4f02/o6DQtZthhTSg5K/iZ6Tb3KrKTuh0EO7yUfG+ybzfszwz8GJGdGt
+uIOY4gNrRh8CgYA2qwmgm1+TSE3wtY/OCs+kwygIi53lKBm9RIigRQzUEZEi0KQG
+D/eUUbQZFTV95q3lI1wucczHzGy2ODj+LnUymPnABGcnLgNib9lqcsAxmxGwCGlL
+gFqdScAksY6YHtsTYTwyvIYOe4s/HZMXHjqh1t9IvPl1T/jdppoFk8NbLQKBgQDU
+JymiAXgGqgnnyFgn/vNC8ZNtUFEqq2sy2tky53lW+B8j8pfT1c6R59AxKiCgfWua
+AjpBUcT2dKjr1zo2xnCz5WdQIxHTvype6DxIhItTl2TFrKHYZL/UQKtDlGXtW+HD
+uvhZk/fQxMaG9J4HSbY1IiEaoF10zdQAjWclia3HiwKBgQDX1piVYGI4c5HtC50h
+NXdbPH+jxgdr3Hj43cki4OaXTg5drX8ZVFc0/fUhASAEG6WbFg9nDU0mH8o+5zoJ
+JehiL/RQOK2s70fitgBYF6RBc+znBp5OEqdQiWn1UlTZ/CdSPfR4vkMADzFEYGxZ
+YVsrUwMSVO65vmGmyfvmIL682g==
+-----END PRIVATE KEY-----
diff --git a/sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/pem/pkcs8-rsa-3072.pem b/sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/pem/pkcs8-rsa-3072.pem
new file mode 100644
index 0000000..9d6a219
--- /dev/null
+++ b/sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/pem/pkcs8-rsa-3072.pem
@@ -0,0 +1,40 @@
+-----BEGIN PRIVATE KEY-----
+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
+uvn49/VWU7/8Ppw/Wg7C+L0B
+-----END PRIVATE KEY-----
diff --git a/sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/pem/pkcs8-rsa-4096.pem b/sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/pem/pkcs8-rsa-4096.pem
new file mode 100644
index 0000000..f625c8e
--- /dev/null
+++ b/sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/pem/pkcs8-rsa-4096.pem
@@ -0,0 +1,52 @@
+-----BEGIN PRIVATE KEY-----
+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
+18uzMDlMbqOoq+IErsvMbHs/fl8GtJ5S
+-----END PRIVATE KEY-----