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 13:00:36 UTC

[mina-sshd] branch master updated (0cc4e60 -> 4c19ef0)

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

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


    from 0cc4e60  Updated CHANGES.md
     new cea78ef  Fixed some code format and definition issues
     new cc7a1a4  [SSHD-989] Moved code that parses PKCS8 encoding to dedicated class
     new 95b1635  [SSHD-989] Implement ECDSA public key recovery from PKCS#8 encoded data
     new 1b2e34a  [SSHD-989] Added verification that parsed/recovered key pairs can be used for signing
     new 4c19ef0  Tighter timeout limit on wait time for SFTP server response to client message

The 5 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 CHANGES.md                                         |   4 +-
 README.md                                          |   2 +
 .../keys/impl/OpenSSHCertificateDecoder.java       |   6 +-
 .../loader/pem/DSSPEMResourceKeyPairParser.java    |   8 +-
 .../loader/pem/ECDSAPEMResourceKeyPairParser.java  |  92 +++++++++---
 .../keys/loader/pem/PEMResourceParserUtils.java    |   4 +
 .../loader/pem/PKCS8PEMResourceKeyPairParser.java  |  93 ++++--------
 .../keys/loader/pem/PKCS8PrivateKeyInfo.java       | 165 +++++++++++++++++++++
 .../loader/pem/RSAPEMResourceKeyPairParser.java    |   8 +-
 .../signature/AbstractSecurityKeySignature.java    |   6 +-
 .../sshd/common/signature/AbstractSignature.java   |  23 ++-
 .../apache/sshd/common/signature/SignatureDSA.java |   2 +-
 .../sshd/common/signature/SignatureECDSA.java      |   2 +-
 .../apache/sshd/common/signature/SignatureRSA.java |  22 ++-
 .../org/apache/sshd/common/util/GenericUtils.java  |   7 +-
 .../common/util/closeable/SequentialCloseable.java |   4 +-
 .../common/util/closeable/SimpleCloseable.java     |   5 +
 .../security/eddsa/EdDSASecurityProviderUtils.java |   7 +-
 .../util/security/eddsa/SignatureEd25519.java      |   3 +-
 .../OpenSSHKeyPairResourceParserDecodingTest.java  |   2 +
 .../OpenSSHKeyPairResourceParserPasswordTest.java  |   1 +
 .../pem/PKCS8PEMResourceKeyPairParserTest.java     |  42 ++++++
 .../common/signature/SignatureRSASHA1Test.java     |   3 +-
 .../common/util/security/SecurityUtilsTest.java    |  10 +-
 .../sshd/util/test/CommonTestSupportUtils.java     |  86 ++++++++++-
 .../apache/sshd/util/test/JUnitTestSupport.java    |   9 ++
 .../subsystem/sftp/impl/DefaultSftpClient.java     |  15 +-
 .../client/subsystem/sftp/SftpTransferTest.java    |  50 ++++---
 28 files changed, 524 insertions(+), 157 deletions(-)
 create mode 100644 sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/PKCS8PrivateKeyInfo.java


[mina-sshd] 02/05: [SSHD-989] Moved code that parses PKCS8 encoding to dedicated class

Posted by lg...@apache.org.
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 cc7a1a425228d3d089ada751973570cad18c2ae5
Author: Lyor Goldstein <lg...@apache.org>
AuthorDate: Fri May 1 12:30:13 2020 +0300

    [SSHD-989] Moved code that parses PKCS8 encoding to dedicated class
---
 .../loader/pem/PKCS8PEMResourceKeyPairParser.java  | 113 +++++++-------
 .../keys/loader/pem/PKCS8PrivateKeyInfo.java       | 165 +++++++++++++++++++++
 .../apache/sshd/util/test/JUnitTestSupport.java    |   1 -
 3 files changed, 215 insertions(+), 64 deletions(-)

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 4a4d5ef..37e6694 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
@@ -21,13 +21,14 @@ 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.interfaces.ECPrivateKey;
+import java.security.interfaces.ECPublicKey;
 import java.security.spec.PKCS8EncodedKeySpec;
 import java.util.Collection;
 import java.util.Collections;
@@ -41,12 +42,11 @@ import org.apache.sshd.common.session.SessionContext;
 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>
+ * @see <a href="https://tools.ietf.org/html/rfc5208">RFC 5208</A>
  */
 public class PKCS8PEMResourceKeyPairParser extends AbstractPEMResourceKeyPairParser {
     // Not exactly according to standard but good enough
@@ -73,12 +73,57 @@ public class PKCS8PEMResourceKeyPairParser extends AbstractPEMResourceKeyPairPar
             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);
+        PKCS8PrivateKeyInfo pkcs8Info = new PKCS8PrivateKeyInfo(encBytes);
+        return extractKeyPairs(
+            session, resourceKey, beginMarker, endMarker,
+            passwordProvider, encBytes, pkcs8Info, headers);
+    }
+
+    public Collection<KeyPair> extractKeyPairs(
+            SessionContext session, NamedResource resourceKey,
+            String beginMarker, String endMarker,
+            FilePasswordProvider passwordProvider, byte[] encBytes,
+            PKCS8PrivateKeyInfo pkcs8Info, Map<String, String> headers)
+            throws IOException, GeneralSecurityException {
+        List<Integer> oidAlgorithm = pkcs8Info.getAlgorithmIdentifier();
         PrivateKey prvKey = decodePEMPrivateKeyPKCS8(oidAlgorithm, encBytes);
-        PublicKey pubKey = ValidateUtils.checkNotNull(KeyUtils.recoverPublicKey(prvKey),
+        PublicKey pubKey = recoverPublicKey(
+            session, resourceKey, beginMarker, endMarker,
+            passwordProvider, headers, encBytes, pkcs8Info, prvKey);
+        ValidateUtils.checkNotNull(pubKey,
                 "Failed to recover public key of OID=%s", oidAlgorithm);
         KeyPair kp = new KeyPair(pubKey, prvKey);
         return Collections.singletonList(kp);
+
+    }
+
+    @SuppressWarnings("checkstyle:ParameterNumber")
+    protected PublicKey recoverPublicKey(
+            SessionContext session, NamedResource resourceKey,
+            String beginMarker, String endMarker,
+            FilePasswordProvider passwordProvider,
+            Map<String, String> headers, byte[] encBytes,
+            PKCS8PrivateKeyInfo pkcs8Info, PrivateKey privateKey)
+            throws IOException, GeneralSecurityException {
+        if (privateKey instanceof ECPrivateKey) {
+            return recoverECPublicKey(
+                session, resourceKey, beginMarker, endMarker,
+                passwordProvider, headers, encBytes, pkcs8Info,
+                (ECPrivateKey) privateKey);
+        }
+
+        return KeyUtils.recoverPublicKey(privateKey);
+    }
+
+    @SuppressWarnings("checkstyle:ParameterNumber")
+    protected ECPublicKey recoverECPublicKey(
+            SessionContext session, NamedResource resourceKey,
+            String beginMarker, String endMarker,
+            FilePasswordProvider passwordProvider,
+            Map<String, String> headers, byte[] encBytes,
+            PKCS8PrivateKeyInfo pkcs8Info, ECPrivateKey privateKey)
+            throws IOException, GeneralSecurityException {
+        throw new NoSuchAlgorithmException("TODO: SSHD-976");
     }
 
     public static PrivateKey decodePEMPrivateKeyPKCS8(List<Integer> oidAlgorithm, byte[] keyBytes)
@@ -100,62 +145,4 @@ public class PKCS8PEMResourceKeyPairParser extends AbstractPEMResourceKeyPairPar
         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();
-            }
-        }
-    }
 }
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
new file mode 100644
index 0000000..873afb2
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/PKCS8PrivateKeyInfo.java
@@ -0,0 +1,165 @@
+/*
+ * 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.StreamCorruptedException;
+import java.math.BigInteger;
+import java.util.List;
+
+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;
+
+/**
+ * <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>
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ * @see <a href="https://tools.ietf.org/html/rfc5208#section-5">RFC 5208 - section 5</a>
+ */
+public class PKCS8PrivateKeyInfo /* TODO Cloneable */ {
+    private BigInteger version;
+    private List<Integer> algorithmIdentifier;
+    private ASN1Object privateKeyBytes;
+
+    public PKCS8PrivateKeyInfo() {
+        super();
+    }
+
+    public PKCS8PrivateKeyInfo(byte[] encBytes) throws IOException {
+        decode(encBytes);
+    }
+
+    public PKCS8PrivateKeyInfo(DERParser parser) throws IOException {
+        this(parser.readObject());
+    }
+
+    public PKCS8PrivateKeyInfo(ASN1Object privateKeyInfo) throws IOException {
+        decode(privateKeyInfo);
+    }
+
+    public BigInteger getVersion() {
+        return version;
+    }
+
+    public void setVersion(BigInteger version) {
+        this.version = version;
+    }
+
+    public List<Integer> getAlgorithmIdentifier() {
+        return algorithmIdentifier;
+    }
+
+    public void setAlgorithmIdentifier(List<Integer> algorithmIdentifier) {
+        this.algorithmIdentifier = algorithmIdentifier;
+    }
+
+    public ASN1Object getPrivateKeyBytes() {
+        return privateKeyBytes;
+    }
+
+    public void setPrivateKeyBytes(ASN1Object privateKeyBytes) {
+        this.privateKeyBytes = privateKeyBytes;
+    }
+
+    public void decode(byte[] encBytes) throws IOException {
+        try (DERParser parser = new DERParser(encBytes)) {
+            decode(parser);
+        }
+    }
+
+    public void decode(DERParser parser) throws IOException {
+        decode(parser.readObject());
+    }
+
+    /**
+     * Decodes the current information with the data from the provided encoding.
+     * <B>Note:</B> User should {@link #clear()} the current information before parsing
+     *
+     * @param privateKeyInfo The {@link ASN1Object} encoding
+     * @throws IOException If failed to parse the encoding
+     */
+    public void decode(ASN1Object privateKeyInfo) throws IOException {
+        try (DERParser parser = privateKeyInfo.createParser()) {
+            ASN1Object versionObject = parser.readObject();
+            if (versionObject == null) {
+                throw new StreamCorruptedException("No version");
+            }
+
+            setVersion(versionObject.asInteger());
+
+            ASN1Object privateKeyAlgorithm = parser.readObject();
+            if (privateKeyAlgorithm == null) {
+                throw new StreamCorruptedException("No private key algorithm");
+            }
+
+            try (DERParser oidParser = privateKeyAlgorithm.createParser()) {
+                ASN1Object oid = oidParser.readObject();
+                setAlgorithmIdentifier(oid.asOID());
+                // TODO add optional algorithm identifier parameters parsing
+            }
+
+            ASN1Object privateKeyData = parser.readObject();
+            if (privateKeyData == null) {
+                throw new StreamCorruptedException("No private key data");
+            }
+
+            ASN1Type objType = privateKeyData.getObjType();
+            if (objType != ASN1Type.OCTET_STRING) {
+                throw new StreamCorruptedException("Private key data not an " + ASN1Type.OCTET_STRING + ": " + objType);
+            }
+
+            setPrivateKeyBytes(privateKeyData);
+            // TODO add implicit attributes parsing
+        }
+    }
+
+    public void clear() {
+        setVersion(null);
+        setAlgorithmIdentifier(null);
+        setPrivateKeyBytes(null);
+    }
+
+    @Override
+    public String toString() {
+        return getClass().getSimpleName()
+            + "[version=" + getVersion()
+            + ", algorithmIdentifier=" + getAlgorithmIdentifier()
+            + "]";
+    }
+}
diff --git a/sshd-common/src/test/java/org/apache/sshd/util/test/JUnitTestSupport.java b/sshd-common/src/test/java/org/apache/sshd/util/test/JUnitTestSupport.java
index 6db9657..84b58c8 100644
--- a/sshd-common/src/test/java/org/apache/sshd/util/test/JUnitTestSupport.java
+++ b/sshd-common/src/test/java/org/apache/sshd/util/test/JUnitTestSupport.java
@@ -447,7 +447,6 @@ public abstract class JUnitTestSupport extends Assert {
             return KeyUtils.EC_ALGORITHM;
         } else {
             return algorithm.toUpperCase(Locale.ENGLISH);
-        }
     }
 
     public static void assertRSAPublicKeyEquals(String message, RSAPublicKey expected, RSAPublicKey actual) {


[mina-sshd] 03/05: [SSHD-989] Implement ECDSA public key recovery from PKCS#8 encoded data

Posted by lg...@apache.org.
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 95b1635454515bde44702f928e99696568540ea3
Author: Lyor Goldstein <lg...@apache.org>
AuthorDate: Fri May 1 18:24:38 2020 +0300

    [SSHD-989] Implement ECDSA public key recovery from PKCS#8 encoded data
---
 CHANGES.md                                         |  6 +-
 .../loader/pem/DSSPEMResourceKeyPairParser.java    |  2 +-
 .../loader/pem/ECDSAPEMResourceKeyPairParser.java  | 75 ++++++++++------------
 .../loader/pem/PKCS8PEMResourceKeyPairParser.java  | 60 ++++++-----------
 .../keys/loader/pem/PKCS8PrivateKeyInfo.java       | 16 ++---
 .../loader/pem/RSAPEMResourceKeyPairParser.java    |  2 +-
 .../org/apache/sshd/common/util/GenericUtils.java  |  2 +-
 .../pem/PKCS8PEMResourceKeyPairParserTest.java     |  6 ++
 .../apache/sshd/util/test/JUnitTestSupport.java    |  1 +
 9 files changed, 75 insertions(+), 95 deletions(-)

diff --git a/CHANGES.md b/CHANGES.md
index 3fb3d27..8e644be 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -53,6 +53,10 @@ as much of the available functionality as possible.
 
 * [SSHD-992](https://issues.apache.org/jira/browse/SSHD-984) - Provide more hooks into the SFTP server subsystem via SftpFileSystemAccessor
 
+<<<<<<< HEAD
 * [SSHD-997](https://issues.apache.org/jira/browse/SSHD-997) - Fixed OpenSSH private key decoders for RSA and Ed25519
 
-* [SSHD-998](https://issues.apache.org/jira/browse/SSHD-998) - Take into account SFTP version preference when establishing initial channel
\ No newline at end of file
+* [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] Implement ECDSA public key recovery from PKCS#8 encoded data
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
index ca4f04a..796bf60 100644
--- 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
@@ -47,7 +47,7 @@ import org.apache.sshd.common.util.security.SecurityUtils;
 
 /**
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
- * @see <a href="https://tools.ietf.org/html/rfc3279#section-2.3.2">RFC-3279 section 2.3.2</a>
+ * @see    <a href="https://tools.ietf.org/html/rfc3279#section-2.3.2">RFC-3279 section 2.3.2</a>
  */
 public class DSSPEMResourceKeyPairParser extends AbstractPEMResourceKeyPairParser {
     // Not exactly according to standard but good enough
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 12b74f9..44d3d51 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
@@ -51,7 +51,7 @@ import org.apache.sshd.common.util.security.SecurityUtils;
 
 /**
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
- * @see <a href="https://tools.ietf.org/html/rfc5915">RFC 5915</a>
+ * @see    <a href="https://tools.ietf.org/html/rfc5915">RFC 5915</a>
  */
 public class ECDSAPEMResourceKeyPairParser extends AbstractPEMResourceKeyPairParser {
     public static final String BEGIN_MARKER = "BEGIN EC PRIVATE KEY";
@@ -78,7 +78,23 @@ public class ECDSAPEMResourceKeyPairParser extends AbstractPEMResourceKeyPairPar
             FilePasswordProvider passwordProvider,
             InputStream stream, Map<String, String> headers)
             throws IOException, GeneralSecurityException {
-        Map.Entry<ECPublicKeySpec, ECPrivateKeySpec> spec = decodeECPrivateKeySpec(stream, false);
+
+        KeyPair kp = parseECKeyPair(stream, false);
+        return Collections.singletonList(kp);
+    }
+
+    public static KeyPair parseECKeyPair(
+            InputStream inputStream, boolean okToClose)
+            throws IOException, GeneralSecurityException {
+        try (DERParser parser = new DERParser(NoCloseInputStream.resolveInputStream(inputStream, okToClose))) {
+            return parseECKeyPair(parser);
+        }
+    }
+
+    public static KeyPair parseECKeyPair(DERParser parser)
+            throws IOException, GeneralSecurityException {
+        ASN1Object sequence = parser.readObject();
+        Map.Entry<ECPublicKeySpec, ECPrivateKeySpec> spec = decodeECPrivateKeySpec(sequence);
         if (!SecurityUtils.isECCSupported()) {
             throw new NoSuchProviderException("ECC not supported");
         }
@@ -86,8 +102,7 @@ public class ECDSAPEMResourceKeyPairParser extends AbstractPEMResourceKeyPairPar
         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);
+        return new KeyPair(pubKey, prvKey);
     }
 
     /**
@@ -121,23 +136,11 @@ public class ECDSAPEMResourceKeyPairParser extends AbstractPEMResourceKeyPairPar
      * </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
+     * @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(
-            InputStream inputStream, boolean okToClose)
-            throws IOException {
-        ASN1Object sequence;
-        try (DERParser parser = new DERParser(NoCloseInputStream.resolveInputStream(inputStream, okToClose))) {
-            sequence = parser.readObject();
-        }
-
-        return decodeECPrivateKeySpec(sequence);
-    }
-
     public static SimpleImmutableEntry<ECPublicKeySpec, ECPrivateKeySpec> decodeECPrivateKeySpec(ASN1Object sequence)
             throws IOException {
         ASN1Type objType = (sequence == null) ? null : sequence.getObjType();
@@ -155,12 +158,8 @@ public class ECDSAPEMResourceKeyPairParser extends AbstractPEMResourceKeyPairPar
             /*
              * According to https://tools.ietf.org/html/rfc5915 - section 3
              *
-             * ECPrivateKey ::= SEQUENCE {
-             *      version        INTEGER { ecPrivkeyVer1(1) } (ecPrivkeyVer1),
-             *      privateKey     OCTET STRING,
-             *      parameters [0] ECParameters {{ NamedCurve }} OPTIONAL,
-             *      publicKey  [1] BIT STRING OPTIONAL
-             * }
+             * ECPrivateKey ::= SEQUENCE { version INTEGER { ecPrivkeyVer1(1) } (ecPrivkeyVer1), privateKey OCTET
+             * STRING, parameters [0] ECParameters {{ NamedCurve }} OPTIONAL, publicKey [1] BIT STRING OPTIONAL }
              */
             ECPoint w = decodeECPublicKeyValue(curve, parser);
             ECPublicKeySpec pubSpec = new ECPublicKeySpec(w, prvSpec.getParams());
@@ -171,12 +170,8 @@ public class ECDSAPEMResourceKeyPairParser extends AbstractPEMResourceKeyPairPar
     /*
      * According to https://tools.ietf.org/html/rfc5915 - section 3
      *
-     * ECPrivateKey ::= SEQUENCE {
-     *      version        INTEGER { ecPrivkeyVer1(1) } (ecPrivkeyVer1),
-     *      privateKey     OCTET STRING,
-     *      parameters [0] ECParameters {{ NamedCurve }} OPTIONAL,
-     *      publicKey  [1] BIT STRING OPTIONAL
-     * }
+     * ECPrivateKey ::= SEQUENCE { version INTEGER { ecPrivkeyVer1(1) } (ecPrivkeyVer1), privateKey OCTET STRING,
+     * parameters [0] ECParameters {{ NamedCurve }} OPTIONAL, publicKey [1] BIT STRING OPTIONAL }
      */
     public static final ECPrivateKeySpec decodeECPrivateKeySpec(DERParser parser) throws IOException {
         // see openssl asn1parse -inform PEM -in ...file... -dump
@@ -188,8 +183,8 @@ public class ECDSAPEMResourceKeyPairParser extends AbstractPEMResourceKeyPairPar
         /*
          * According to https://tools.ietf.org/html/rfc5915 - section 3
          *
-         *      For this version of the document, it SHALL be set to ecPrivkeyVer1,
-         *      which is of type INTEGER and whose value is one (1)
+         * For this version of the document, it SHALL be set to ecPrivkeyVer1, which is of type INTEGER and whose value
+         * is one (1)
          */
         BigInteger version = versionObject.asInteger();
         if (!BigInteger.ONE.equals(version)) {
@@ -209,14 +204,11 @@ public class ECDSAPEMResourceKeyPairParser extends AbstractPEMResourceKeyPairPar
         /*
          * According to https://tools.ietf.org/html/rfc5915 - section 3
          *
-         *      parameters specifies the elliptic curve domain parameters
-         *      associated to the private key.  The type ECParameters is discussed
-         *      in [RFC5480].  As specified in [RFC5480], only the namedCurve
-         *      CHOICE is permitted.  namedCurve is an object identifier that
-         *      fully identifies the required values for a particular set of
-         *      elliptic 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.
+         * parameters specifies the elliptic curve domain parameters associated to the private key. The type
+         * ECParameters is discussed in [RFC5480]. As specified in [RFC5480], only the namedCurve CHOICE is permitted.
+         * namedCurve is an object identifier that fully identifies the required values for a particular set of elliptic
+         * 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();
         if (paramsObject == null) {
@@ -271,9 +263,8 @@ 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();
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 37e6694..f871a3a 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
@@ -27,8 +27,6 @@ import java.security.KeyPair;
 import java.security.NoSuchAlgorithmException;
 import java.security.PrivateKey;
 import java.security.PublicKey;
-import java.security.interfaces.ECPrivateKey;
-import java.security.interfaces.ECPublicKey;
 import java.security.spec.PKCS8EncodedKeySpec;
 import java.util.Collection;
 import java.util.Collections;
@@ -42,11 +40,13 @@ import org.apache.sshd.common.session.SessionContext;
 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>
- * @see <a href="https://tools.ietf.org/html/rfc5208">RFC 5208</A>
+ * @see    <a href="https://tools.ietf.org/html/rfc5208">RFC 5208</A>
  */
 public class PKCS8PEMResourceKeyPairParser extends AbstractPEMResourceKeyPairParser {
     // Not exactly according to standard but good enough
@@ -75,8 +75,8 @@ public class PKCS8PEMResourceKeyPairParser extends AbstractPEMResourceKeyPairPar
         byte[] encBytes = IoUtils.toByteArray(stream);
         PKCS8PrivateKeyInfo pkcs8Info = new PKCS8PrivateKeyInfo(encBytes);
         return extractKeyPairs(
-            session, resourceKey, beginMarker, endMarker,
-            passwordProvider, encBytes, pkcs8Info, headers);
+                session, resourceKey, beginMarker, endMarker,
+                passwordProvider, encBytes, pkcs8Info, headers);
     }
 
     public Collection<KeyPair> extractKeyPairs(
@@ -86,44 +86,22 @@ public class PKCS8PEMResourceKeyPairParser extends AbstractPEMResourceKeyPairPar
             PKCS8PrivateKeyInfo pkcs8Info, Map<String, String> headers)
             throws IOException, GeneralSecurityException {
         List<Integer> oidAlgorithm = pkcs8Info.getAlgorithmIdentifier();
-        PrivateKey prvKey = decodePEMPrivateKeyPKCS8(oidAlgorithm, encBytes);
-        PublicKey pubKey = recoverPublicKey(
-            session, resourceKey, beginMarker, endMarker,
-            passwordProvider, headers, encBytes, pkcs8Info, prvKey);
-        ValidateUtils.checkNotNull(pubKey,
-                "Failed to recover public key of OID=%s", oidAlgorithm);
-        KeyPair kp = new KeyPair(pubKey, prvKey);
-        return Collections.singletonList(kp);
-
-    }
-
-    @SuppressWarnings("checkstyle:ParameterNumber")
-    protected PublicKey recoverPublicKey(
-            SessionContext session, NamedResource resourceKey,
-            String beginMarker, String endMarker,
-            FilePasswordProvider passwordProvider,
-            Map<String, String> headers, byte[] encBytes,
-            PKCS8PrivateKeyInfo pkcs8Info, PrivateKey privateKey)
-            throws IOException, GeneralSecurityException {
-        if (privateKey instanceof ECPrivateKey) {
-            return recoverECPublicKey(
-                session, resourceKey, beginMarker, endMarker,
-                passwordProvider, headers, encBytes, pkcs8Info,
-                (ECPrivateKey) privateKey);
+        String oid = GenericUtils.join(oidAlgorithm, '.');
+        KeyPair kp;
+        if (SecurityUtils.isECCSupported()
+                && ECDSAPEMResourceKeyPairParser.ECDSA_OID.equals(oid)) {
+            ASN1Object privateKeyBytes = pkcs8Info.getPrivateKeyBytes();
+            try (DERParser parser = privateKeyBytes.createParser()) {
+                kp = ECDSAPEMResourceKeyPairParser.parseECKeyPair(parser);
+            }
+        } else {
+            PrivateKey prvKey = decodePEMPrivateKeyPKCS8(oidAlgorithm, encBytes);
+            PublicKey pubKey = ValidateUtils.checkNotNull(KeyUtils.recoverPublicKey(prvKey),
+                    "Failed to recover public key of OID=%s", oidAlgorithm);
+            kp = new KeyPair(pubKey, prvKey);
         }
 
-        return KeyUtils.recoverPublicKey(privateKey);
-    }
-
-    @SuppressWarnings("checkstyle:ParameterNumber")
-    protected ECPublicKey recoverECPublicKey(
-            SessionContext session, NamedResource resourceKey,
-            String beginMarker, String endMarker,
-            FilePasswordProvider passwordProvider,
-            Map<String, String> headers, byte[] encBytes,
-            PKCS8PrivateKeyInfo pkcs8Info, ECPrivateKey privateKey)
-            throws IOException, GeneralSecurityException {
-        throw new NoSuchAlgorithmException("TODO: SSHD-976");
+        return Collections.singletonList(kp);
     }
 
     public static PrivateKey decodePEMPrivateKeyPKCS8(List<Integer> oidAlgorithm, byte[] keyBytes)
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 873afb2..48917a5 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
@@ -50,7 +50,7 @@ import org.apache.sshd.common.util.io.der.DERParser;
  * </PRE>
  *
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
- * @see <a href="https://tools.ietf.org/html/rfc5208#section-5">RFC 5208 - section 5</a>
+ * @see    <a href="https://tools.ietf.org/html/rfc5208#section-5">RFC 5208 - section 5</a>
  */
 public class PKCS8PrivateKeyInfo /* TODO Cloneable */ {
     private BigInteger version;
@@ -108,11 +108,11 @@ public class PKCS8PrivateKeyInfo /* TODO Cloneable */ {
     }
 
     /**
-     * Decodes the current information with the data from the provided encoding.
-     * <B>Note:</B> User should {@link #clear()} the current information before parsing
+     * Decodes the current information with the data from the provided encoding. <B>Note:</B> User should
+     * {@link #clear()} the current information before parsing
      *
-     * @param privateKeyInfo The {@link ASN1Object} encoding
-     * @throws IOException If failed to parse the encoding
+     * @param  privateKeyInfo The {@link ASN1Object} encoding
+     * @throws IOException    If failed to parse the encoding
      */
     public void decode(ASN1Object privateKeyInfo) throws IOException {
         try (DERParser parser = privateKeyInfo.createParser()) {
@@ -158,8 +158,8 @@ public class PKCS8PrivateKeyInfo /* TODO Cloneable */ {
     @Override
     public String toString() {
         return getClass().getSimpleName()
-            + "[version=" + getVersion()
-            + ", algorithmIdentifier=" + getAlgorithmIdentifier()
-            + "]";
+               + "[version=" + getVersion()
+               + ", algorithmIdentifier=" + getAlgorithmIdentifier()
+               + "]";
     }
 }
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
index d599afe..161a0ac 100644
--- 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
@@ -48,7 +48,7 @@ import org.apache.sshd.common.util.security.SecurityUtils;
 
 /**
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
- * @see <a href="https://tools.ietf.org/html/rfc3279#section-2.3.1">RFC-3279 section 2.3.1</a>
+ * @see    <a href="https://tools.ietf.org/html/rfc3279#section-2.3.1">RFC-3279 section 2.3.1</a>
  */
 public class RSAPEMResourceKeyPairParser extends AbstractPEMResourceKeyPairParser {
     // Not exactly according to standard but good enough
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/GenericUtils.java b/sshd-common/src/main/java/org/apache/sshd/common/util/GenericUtils.java
index 4ed0514..1bc8bfa 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/util/GenericUtils.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/GenericUtils.java
@@ -1015,7 +1015,7 @@ public final class GenericUtils {
     /**
      * The delegate Suppliers get() method is called exactly once and the result is cached.
      *
-     * @param <T> Generic type of supplied value
+     * @param  <T>      Generic type of supplied value
      * @param  delegate The actual Supplier
      * @return          The memoized Supplier
      */
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 4c6fdd8..f078c23 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
@@ -32,6 +32,7 @@ import java.util.List;
 import org.apache.commons.ssl.PEMItem;
 import org.apache.commons.ssl.PEMUtil;
 import org.apache.sshd.common.NamedResource;
+import org.apache.sshd.common.cipher.ECCurves;
 import org.apache.sshd.common.config.keys.KeyUtils;
 import org.apache.sshd.common.util.GenericUtils;
 import org.apache.sshd.common.util.security.SecurityUtils;
@@ -74,6 +75,11 @@ public class PKCS8PEMResourceKeyPairParserTest extends JUnitTestSupport {
         for (Integer ks : DSS_SIZES) {
             params.add(new Object[] { KeyUtils.DSS_ALGORITHM, ks });
         }
+        if (SecurityUtils.isECCSupported()) {
+            for (ECCurves curve : ECCurves.VALUES) {
+                params.add(new Object[] { KeyUtils.EC_ALGORITHM, curve.getKeySize() });
+            }
+        }
         return params;
     }
 
diff --git a/sshd-common/src/test/java/org/apache/sshd/util/test/JUnitTestSupport.java b/sshd-common/src/test/java/org/apache/sshd/util/test/JUnitTestSupport.java
index 84b58c8..6db9657 100644
--- a/sshd-common/src/test/java/org/apache/sshd/util/test/JUnitTestSupport.java
+++ b/sshd-common/src/test/java/org/apache/sshd/util/test/JUnitTestSupport.java
@@ -447,6 +447,7 @@ public abstract class JUnitTestSupport extends Assert {
             return KeyUtils.EC_ALGORITHM;
         } else {
             return algorithm.toUpperCase(Locale.ENGLISH);
+        }
     }
 
     public static void assertRSAPublicKeyEquals(String message, RSAPublicKey expected, RSAPublicKey actual) {


[mina-sshd] 04/05: [SSHD-989] Added verification that parsed/recovered key pairs can be used for signing

Posted by lg...@apache.org.
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 1b2e34a11636ad62ae9092dc30e2c320d5e23dcf
Author: Lyor Goldstein <lg...@apache.org>
AuthorDate: Fri May 22 08:59:15 2020 +0300

    [SSHD-989] Added verification that parsed/recovered key pairs can be used for signing
---
 CHANGES.md                                         |  4 +-
 .../loader/pem/ECDSAPEMResourceKeyPairParser.java  |  5 ++
 .../sshd/common/signature/AbstractSignature.java   | 23 ++++--
 .../apache/sshd/common/signature/SignatureDSA.java |  2 +-
 .../sshd/common/signature/SignatureECDSA.java      |  2 +-
 .../apache/sshd/common/signature/SignatureRSA.java | 22 ++++--
 .../security/eddsa/EdDSASecurityProviderUtils.java |  7 +-
 .../util/security/eddsa/SignatureEd25519.java      |  3 +-
 .../OpenSSHKeyPairResourceParserDecodingTest.java  |  2 +
 .../OpenSSHKeyPairResourceParserPasswordTest.java  |  1 +
 .../pem/PKCS8PEMResourceKeyPairParserTest.java     | 36 +++++++++
 .../common/signature/SignatureRSASHA1Test.java     |  3 +-
 .../common/util/security/SecurityUtilsTest.java    | 10 +--
 .../sshd/util/test/CommonTestSupportUtils.java     | 86 +++++++++++++++++++++-
 .../apache/sshd/util/test/JUnitTestSupport.java    |  9 +++
 15 files changed, 191 insertions(+), 24 deletions(-)

diff --git a/CHANGES.md b/CHANGES.md
index 8e644be..1495950 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -53,10 +53,8 @@ as much of the available functionality as possible.
 
 * [SSHD-992](https://issues.apache.org/jira/browse/SSHD-984) - Provide more hooks into the SFTP server subsystem via SftpFileSystemAccessor
 
-<<<<<<< HEAD
 * [SSHD-997](https://issues.apache.org/jira/browse/SSHD-997) - Fixed OpenSSH private key decoders for RSA and Ed25519
 
 * [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] Implement ECDSA public key recovery from PKCS#8 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 44d3d51..13b3f91 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
@@ -224,6 +224,11 @@ public class ECDSAPEMResourceKeyPairParser extends AbstractPEMResourceKeyPairPar
                 throw new StreamCorruptedException("Missing named curve parameter");
             }
 
+            /*
+             * 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
+             */
+
             curveOID = namedCurve.asOID();
         }
 
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/signature/AbstractSignature.java b/sshd-common/src/main/java/org/apache/sshd/common/signature/AbstractSignature.java
index 8381d32..d167788 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/signature/AbstractSignature.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/signature/AbstractSignature.java
@@ -25,9 +25,13 @@ import java.security.PrivateKey;
 import java.security.PublicKey;
 import java.security.SignatureException;
 import java.util.AbstractMap.SimpleImmutableEntry;
+import java.util.Collection;
+import java.util.Map;
 import java.util.Objects;
+import java.util.function.Predicate;
 
 import org.apache.sshd.common.session.SessionContext;
+import org.apache.sshd.common.util.GenericUtils;
 import org.apache.sshd.common.util.NumberUtils;
 import org.apache.sshd.common.util.ValidateUtils;
 import org.apache.sshd.common.util.buffer.BufferUtils;
@@ -109,12 +113,17 @@ public abstract class AbstractSignature implements Signature {
     /**
      * Makes an attempt to detect if the signature is encoded or pure data
      *
-     * @param  sig The original signature
-     * @return     A {@link SimpleImmutableEntry} where first value is the key type and second value is the data -
-     *             {@code null} if not encoded
+     * @param  sig           The original signature
+     * @param  expectedTypes The expected encoded key types
+     * @return               A {@link SimpleImmutableEntry} where first value is the key type and second value is the
+     *                       data - {@code null} if not encoded
      */
-    protected SimpleImmutableEntry<String, byte[]> extractEncodedSignature(byte[] sig) {
-        final int dataLen = NumberUtils.length(sig);
+    protected Map.Entry<String, byte[]> extractEncodedSignature(byte[] sig, Collection<String> expectedTypes) {
+        return GenericUtils.isEmpty(expectedTypes) ? null : extractEncodedSignature(sig, k -> expectedTypes.contains(k));
+    }
+
+    protected Map.Entry<String, byte[]> extractEncodedSignature(byte[] sig, Predicate<? super String> typeSelector) {
+        int dataLen = NumberUtils.length(sig);
         // if it is encoded then we must have at least 2 UINT32 values
         if (dataLen < (2 * Integer.BYTES)) {
             return null;
@@ -141,6 +150,10 @@ public abstract class AbstractSignature implements Signature {
         }
 
         String keyType = new String(sig, keyTypeStartPos, (int) keyTypeLen, StandardCharsets.UTF_8);
+        if (!typeSelector.test(keyType)) {
+            return null;
+        }
+
         byte[] data = new byte[(int) dataBytesLen];
         System.arraycopy(sig, keyTypeEndPos + Integer.BYTES, data, 0, (int) dataBytesLen);
         return new SimpleImmutableEntry<>(keyType, data);
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/signature/SignatureDSA.java b/sshd-common/src/main/java/org/apache/sshd/common/signature/SignatureDSA.java
index d44d222..4ee5785 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/signature/SignatureDSA.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/signature/SignatureDSA.java
@@ -102,7 +102,7 @@ public class SignatureDSA extends AbstractSignature {
 
         if (sigLen != DSA_SIGNATURE_LENGTH) {
             // probably some encoded data
-            Map.Entry<String, byte[]> encoding = extractEncodedSignature(sig);
+            Map.Entry<String, byte[]> encoding = extractEncodedSignature(sig, k -> KeyPairProvider.SSH_DSS.equalsIgnoreCase(k));
             if (encoding != null) {
                 String keyType = encoding.getKey();
                 ValidateUtils.checkTrue(
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/signature/SignatureECDSA.java b/sshd-common/src/main/java/org/apache/sshd/common/signature/SignatureECDSA.java
index e8e705b..33e4251 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/signature/SignatureECDSA.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/signature/SignatureECDSA.java
@@ -103,7 +103,7 @@ public class SignatureECDSA extends AbstractSignature {
     @Override
     public boolean verify(SessionContext session, byte[] sig) throws Exception {
         byte[] data = sig;
-        Map.Entry<String, byte[]> encoding = extractEncodedSignature(data);
+        Map.Entry<String, byte[]> encoding = extractEncodedSignature(data, ECCurves.KEY_TYPES);
         if (encoding != null) {
             String keyType = encoding.getKey();
             ECCurves curve = ECCurves.fromKeyType(keyType);
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/signature/SignatureRSA.java b/sshd-common/src/main/java/org/apache/sshd/common/signature/SignatureRSA.java
index a41a89b..0639a45 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/signature/SignatureRSA.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/signature/SignatureRSA.java
@@ -21,7 +21,12 @@ package org.apache.sshd.common.signature;
 import java.math.BigInteger;
 import java.security.PublicKey;
 import java.security.interfaces.RSAKey;
+import java.util.Collections;
 import java.util.Map;
+import java.util.NavigableSet;
+import java.util.TreeSet;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 import org.apache.sshd.common.config.keys.KeyUtils;
 import org.apache.sshd.common.keyprovider.KeyPairProvider;
@@ -35,6 +40,16 @@ import org.apache.sshd.common.util.ValidateUtils;
  * @see    <A HREF="https://tools.ietf.org/html/rfc4253#section-6.6">RFC4253 section 6.6</A>
  */
 public abstract class SignatureRSA extends AbstractSignature {
+    public static final NavigableSet<String> SUPPORTED_KEY_TYPES = Collections.unmodifiableNavigableSet(
+            Stream.of(
+                    KeyPairProvider.SSH_RSA,
+                    KeyPairProvider.SSH_RSA_CERT,
+                    KeyUtils.RSA_SHA256_KEY_TYPE_ALIAS,
+                    KeyUtils.RSA_SHA512_KEY_TYPE_ALIAS,
+                    KeyUtils.RSA_SHA256_CERT_TYPE_ALIAS,
+                    KeyUtils.RSA_SHA512_CERT_TYPE_ALIAS)
+                    .collect(Collectors.toCollection(() -> new TreeSet<>(String.CASE_INSENSITIVE_ORDER))));
+
     private int verifierSignatureSize = -1;
 
     protected SignatureRSA(String algorithm) {
@@ -64,7 +79,7 @@ public abstract class SignatureRSA extends AbstractSignature {
     @Override
     public boolean verify(SessionContext session, byte[] sig) throws Exception {
         byte[] data = sig;
-        Map.Entry<String, byte[]> encoding = extractEncodedSignature(data);
+        Map.Entry<String, byte[]> encoding = extractEncodedSignature(data, SUPPORTED_KEY_TYPES);
         if (encoding != null) {
             String keyType = encoding.getKey();
             /*
@@ -76,10 +91,7 @@ public abstract class SignatureRSA extends AbstractSignature {
              * another variant that corresponds to a good-faith implementation and is considered safe to accept.
              */
             String canonicalName = KeyUtils.getCanonicalKeyType(keyType);
-            if ((!KeyPairProvider.SSH_RSA.equals(canonicalName))
-                    && (!KeyPairProvider.SSH_RSA_CERT.equals(canonicalName))) {
-                throw new IllegalArgumentException("Mismatched key type: " + keyType);
-            }
+            ValidateUtils.checkTrue(SUPPORTED_KEY_TYPES.contains(canonicalName), "Mismatched key type: %s", keyType);
             data = encoding.getValue();
         }
 
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/EdDSASecurityProviderUtils.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/EdDSASecurityProviderUtils.java
index 25dab72..a142196 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/EdDSASecurityProviderUtils.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/EdDSASecurityProviderUtils.java
@@ -48,6 +48,7 @@ import org.apache.sshd.common.util.security.SecurityUtils;
 public final class EdDSASecurityProviderUtils {
     // See EdDSANamedCurveTable
     public static final String CURVE_ED25519_SHA512 = "Ed25519";
+    public static final int KEY_SIZE = 256;
 
     private EdDSASecurityProviderUtils() {
         throw new UnsupportedOperationException("No instance");
@@ -61,8 +62,12 @@ public final class EdDSASecurityProviderUtils {
         return EdDSAPrivateKey.class;
     }
 
+    public static boolean isEDDSAKey(Key key) {
+        return getEDDSAKeySize(key) == KEY_SIZE;
+    }
+
     public static int getEDDSAKeySize(Key key) {
-        return (SecurityUtils.isEDDSACurveSupported() && (key instanceof EdDSAKey)) ? 256 : -1;
+        return (SecurityUtils.isEDDSACurveSupported() && (key instanceof EdDSAKey)) ? KEY_SIZE : -1;
     }
 
     public static boolean compareEDDSAPPublicKeys(PublicKey k1, PublicKey k2) {
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/SignatureEd25519.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/SignatureEd25519.java
index 16d8cf8..f5dcba5 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/SignatureEd25519.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/SignatureEd25519.java
@@ -37,7 +37,8 @@ public class SignatureEd25519 extends AbstractSignature {
     @Override
     public boolean verify(SessionContext session, byte[] sig) throws Exception {
         byte[] data = sig;
-        Map.Entry<String, byte[]> encoding = extractEncodedSignature(data);
+        Map.Entry<String, byte[]> encoding
+                = extractEncodedSignature(data, k -> KeyPairProvider.SSH_ED25519.equalsIgnoreCase(k));
         if (encoding != null) {
             String keyType = encoding.getKey();
             ValidateUtils.checkTrue(
diff --git a/sshd-common/src/test/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHKeyPairResourceParserDecodingTest.java b/sshd-common/src/test/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHKeyPairResourceParserDecodingTest.java
index fd16ef2..a41cd93 100644
--- a/sshd-common/src/test/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHKeyPairResourceParserDecodingTest.java
+++ b/sshd-common/src/test/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHKeyPairResourceParserDecodingTest.java
@@ -91,6 +91,8 @@ public class OpenSSHKeyPairResourceParserDecodingTest extends OpenSSHKeyPairReso
                 fail("Unsupported key type name (" + pubName + "): " + supportedTypeNames);
             }
 
+            validateKeyPairSignable(pubName, kp);
+
             @SuppressWarnings("rawtypes")
             PrivateKeyEntryDecoder decoder = OpenSSHKeyPairResourceParser.getPrivateKeyEntryDecoder(prvKey);
             assertNotNull("No private key decoder", decoder);
diff --git a/sshd-common/src/test/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHKeyPairResourceParserPasswordTest.java b/sshd-common/src/test/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHKeyPairResourceParserPasswordTest.java
index 60acbce..b5033c4 100644
--- a/sshd-common/src/test/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHKeyPairResourceParserPasswordTest.java
+++ b/sshd-common/src/test/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHKeyPairResourceParserPasswordTest.java
@@ -163,6 +163,7 @@ public class OpenSSHKeyPairResourceParserPasswordTest extends OpenSSHKeyPairReso
             case RETRY:
                 assertEquals("Mismatched pairs count", 1, GenericUtils.size(pairs));
                 assertEquals("Mismatched retries count", MAX_RETRIES, retriesCount.getAndSet(0));
+                validateKeyPairSignable(resourceKey, GenericUtils.head(pairs));
                 break;
 
             default:
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 f078c23..4dbdcaf 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
@@ -21,10 +21,12 @@ package org.apache.sshd.common.config.keys.loader.pem;
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
+import java.net.URL;
 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;
@@ -39,7 +41,9 @@ import org.apache.sshd.common.util.security.SecurityUtils;
 import org.apache.sshd.util.test.JUnit4ClassRunnerWithParametersFactory;
 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;
@@ -110,6 +114,38 @@ 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
+    @Test // see SSHD-989
+    @Ignore("WIP")
+    public void testPKCS8FileParsing() throws Exception {
+        String resourceKey = "pkcs8-" + algorithm.toLowerCase() + "-" + keySize + ".pem";
+        URL url = getClass().getResource(resourceKey);
+        Assume.assumeTrue("No test file=" + resourceKey, url != null);
+
+        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
+    }
+
     @Override
     public String toString() {
         return getClass().getSimpleName() + "[" + algorithm + "/" + keySize + "]";
diff --git a/sshd-common/src/test/java/org/apache/sshd/common/signature/SignatureRSASHA1Test.java b/sshd-common/src/test/java/org/apache/sshd/common/signature/SignatureRSASHA1Test.java
index 9fb12dd..7bb3a15 100644
--- a/sshd-common/src/test/java/org/apache/sshd/common/signature/SignatureRSASHA1Test.java
+++ b/sshd-common/src/test/java/org/apache/sshd/common/signature/SignatureRSASHA1Test.java
@@ -118,7 +118,8 @@ public class SignatureRSASHA1Test extends JUnitTestSupport {
         assertTrue("Verifier signature size not initialized", vSize > 0);
 
         // make sure padding is required
-        Map.Entry<String, byte[]> encoding = rsa.extractEncodedSignature(TEST_SIGNATURE);
+        Map.Entry<String, byte[]> encoding = rsa.extractEncodedSignature(
+                TEST_SIGNATURE, SignatureRSA.SUPPORTED_KEY_TYPES);
         assertNotNull("Signature is not encoded", encoding);
         byte[] data = encoding.getValue();
         assertTrue("Signature data size (" + data.length + ") not below verifier size (" + vSize + ")", data.length < vSize);
diff --git a/sshd-common/src/test/java/org/apache/sshd/common/util/security/SecurityUtilsTest.java b/sshd-common/src/test/java/org/apache/sshd/common/util/security/SecurityUtilsTest.java
index 2d7ea79..ecf7ac8 100644
--- a/sshd-common/src/test/java/org/apache/sshd/common/util/security/SecurityUtilsTest.java
+++ b/sshd-common/src/test/java/org/apache/sshd/common/util/security/SecurityUtilsTest.java
@@ -108,8 +108,7 @@ public class SecurityUtilsTest extends JUnitTestSupport {
         }
     }
 
-    private KeyPair testLoadEncryptedRSAPrivateKey(String algorithm)
-            throws IOException, GeneralSecurityException {
+    private KeyPair testLoadEncryptedRSAPrivateKey(String algorithm) throws Exception {
         return testLoadRSAPrivateKey(DEFAULT_PASSWORD.replace(' ', '-') + "-RSA-" + algorithm.toUpperCase() + "-key");
     }
 
@@ -140,17 +139,17 @@ public class SecurityUtilsTest extends JUnitTestSupport {
         }
     }
 
-    private KeyPair testLoadECPrivateKey(String name) throws IOException, GeneralSecurityException {
+    private KeyPair testLoadECPrivateKey(String name) throws Exception {
         return testLoadPrivateKey(name, ECPublicKey.class, ECPrivateKey.class);
     }
 
-    private KeyPair testLoadRSAPrivateKey(String name) throws IOException, GeneralSecurityException {
+    private KeyPair testLoadRSAPrivateKey(String name) throws Exception {
         return testLoadPrivateKey(name, RSAPublicKey.class, RSAPrivateKey.class);
     }
 
     private KeyPair testLoadPrivateKey(
             String name, Class<? extends PublicKey> pubType, Class<? extends PrivateKey> prvType)
-            throws IOException, GeneralSecurityException {
+            throws Exception {
         Path folder = getTestResourcesFolder();
         Path file = folder.resolve(name);
         KeyPair kpFile = testLoadPrivateKeyFile(file, pubType, prvType);
@@ -167,6 +166,7 @@ public class SecurityUtilsTest extends JUnitTestSupport {
         Package pkg = clazz.getPackage();
         KeyPair kpResource = testLoadPrivateKeyResource(pkg.getName().replace('.', '/') + "/" + name, pubType, prvType);
         assertTrue(name + ": Mismatched key file vs. resource values", KeyUtils.compareKeyPairs(kpFile, kpResource));
+        validateKeyPairSignable(name, kpResource);
         return kpResource;
     }
 
diff --git a/sshd-common/src/test/java/org/apache/sshd/util/test/CommonTestSupportUtils.java b/sshd-common/src/test/java/org/apache/sshd/util/test/CommonTestSupportUtils.java
index 43f635a..d39e76c 100644
--- a/sshd-common/src/test/java/org/apache/sshd/util/test/CommonTestSupportUtils.java
+++ b/sshd-common/src/test/java/org/apache/sshd/util/test/CommonTestSupportUtils.java
@@ -26,17 +26,25 @@ import java.net.MalformedURLException;
 import java.net.URI;
 import java.net.URISyntaxException;
 import java.net.URL;
+import java.nio.ByteBuffer;
+import java.nio.channels.ByteChannel;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.DirectoryStream;
 import java.nio.file.Files;
 import java.nio.file.LinkOption;
 import java.nio.file.Path;
 import java.nio.file.Paths;
+import java.nio.file.StandardOpenOption;
 import java.security.CodeSource;
 import java.security.GeneralSecurityException;
 import java.security.KeyPair;
 import java.security.KeyPairGenerator;
+import java.security.PrivateKey;
 import java.security.ProtectionDomain;
+import java.security.PublicKey;
+import java.security.interfaces.DSAPrivateKey;
+import java.security.interfaces.ECKey;
+import java.security.interfaces.RSAPrivateKey;
 import java.security.spec.InvalidKeySpecException;
 import java.util.Arrays;
 import java.util.Collection;
@@ -45,9 +53,11 @@ import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Optional;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.atomic.AtomicReference;
 
+import net.i2p.crypto.eddsa.EdDSAPrivateKey;
 import org.apache.sshd.common.Factory;
 import org.apache.sshd.common.cipher.ECCurves;
 import org.apache.sshd.common.config.keys.KeyUtils;
@@ -56,6 +66,8 @@ import org.apache.sshd.common.keyprovider.KeyIdentityProvider;
 import org.apache.sshd.common.keyprovider.KeyPairProvider;
 import org.apache.sshd.common.keyprovider.KeyPairProviderHolder;
 import org.apache.sshd.common.random.Random;
+import org.apache.sshd.common.signature.BuiltinSignatures;
+import org.apache.sshd.common.signature.Signature;
 import org.apache.sshd.common.util.GenericUtils;
 import org.apache.sshd.common.util.ValidateUtils;
 import org.apache.sshd.common.util.io.IoUtils;
@@ -532,7 +544,7 @@ public final class CommonTestSupportUtils {
         }
     }
 
-    private static <P extends KeyIdentityProvider> P validateKeyPairProvider(P provider) {
+    public static <P extends KeyIdentityProvider> P validateKeyPairProvider(P provider) {
         Objects.requireNonNull(provider, "No provider");
 
         // get the I/O out of the way
@@ -571,4 +583,76 @@ public final class CommonTestSupportUtils {
             return bytes;
         }
     }
+
+    /**
+     * Checks that the key pair can be used to successfully validate a signature
+     *
+     * @param  kp        The {@link KeyPair}
+     * @return           An {@link Optional} holding the verification result - if empty then no appropriate signer was
+     *                   found for the keys.
+     * @throws Exception If failed to generate the signature
+     */
+    public static Optional<Boolean> verifySignatureMatch(KeyPair kp) throws Exception {
+        return verifySignatureMatch(kp.getPrivate(), kp.getPublic());
+    }
+
+    public static Optional<Boolean> verifySignatureMatch(
+            PrivateKey privateKey, PublicKey publicKey)
+            throws Exception {
+        Objects.requireNonNull(privateKey, "No private key provided");
+        Objects.requireNonNull(publicKey, "No public key provided");
+
+        // Use check only the private key so we can detect if "mixed" keys are used by failing the verification
+        if (privateKey instanceof RSAPrivateKey) {
+            return Optional.of(verifySignatureMatch(privateKey, publicKey, BuiltinSignatures.rsa));
+        } else if (privateKey instanceof DSAPrivateKey) {
+            return Optional.of(verifySignatureMatch(privateKey, publicKey, BuiltinSignatures.dsa));
+        } else if (SecurityUtils.isECCSupported() && (privateKey instanceof ECKey)) {
+            ECCurves curve = ECCurves.fromECKey((ECKey) privateKey);
+            ValidateUtils.checkNotNull(curve, "Unsupported EC key: %s", privateKey);
+            switch (curve) {
+                case nistp256:
+                    return Optional.of(verifySignatureMatch(privateKey, publicKey, BuiltinSignatures.nistp256));
+                case nistp384:
+                    return Optional.of(verifySignatureMatch(privateKey, publicKey, BuiltinSignatures.nistp384));
+                case nistp521:
+                    return Optional.of(verifySignatureMatch(privateKey, publicKey, BuiltinSignatures.nistp521));
+                default: // ignore
+            }
+        } else if (SecurityUtils.isEDDSACurveSupported() && (privateKey instanceof EdDSAPrivateKey)) {
+            return Optional.of(verifySignatureMatch(privateKey, publicKey, BuiltinSignatures.ed25519));
+        }
+
+        return Optional.empty();
+    }
+
+    public static boolean verifySignatureMatch(
+            PrivateKey privateKey, PublicKey publicKey, Factory<? extends Signature> factory)
+            throws Exception {
+        Signature signer = factory.create();
+        signer.initSigner(null, privateKey);
+
+        byte[] msg = ("[" + privateKey + "][" + publicKey + "]@" + signer).getBytes(StandardCharsets.UTF_8);
+        signer.update(null, msg);
+        byte[] signature = signer.sign(null);
+
+        Signature verifier = factory.create();
+        verifier.initVerifier(null, publicKey);
+        verifier.update(null, msg);
+        return verifier.verify(null, signature);
+    }
+
+    // clears the sensitive data regardless of success/failure
+    public static void writeSensitiveDataToFile(Path file, byte[] sensitiveData)
+            throws IOException {
+        try (ByteChannel out = Files.newByteChannel(file,
+                StandardOpenOption.CREATE, StandardOpenOption.WRITE)) {
+            ByteBuffer buf = ByteBuffer.wrap(sensitiveData);
+            while (buf.hasRemaining()) {
+                out.write(buf);
+            }
+        } finally {
+            Arrays.fill(sensitiveData, (byte) 0);
+        }
+    }
 }
diff --git a/sshd-common/src/test/java/org/apache/sshd/util/test/JUnitTestSupport.java b/sshd-common/src/test/java/org/apache/sshd/util/test/JUnitTestSupport.java
index 6db9657..aede6de 100644
--- a/sshd-common/src/test/java/org/apache/sshd/util/test/JUnitTestSupport.java
+++ b/sshd-common/src/test/java/org/apache/sshd/util/test/JUnitTestSupport.java
@@ -52,6 +52,7 @@ import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Optional;
 import java.util.concurrent.TimeUnit;
 import java.util.function.BiPredicate;
 
@@ -440,6 +441,14 @@ public abstract class JUnitTestSupport extends Assert {
         }
     }
 
+    public static KeyPair validateKeyPairSignable(Object hint, KeyPair kp) throws Exception {
+        assertNotNull(hint + ": no key pair provided", kp);
+        Optional<Boolean> signable = CommonTestSupportUtils.verifySignatureMatch(kp);
+        // if no result then assume "OK"
+        assertTrue(hint + ": Failed to validate signature", signable.orElse(Boolean.TRUE));
+        return kp;
+    }
+
     public static String resolveEffectiveAlgorithm(String algorithm) {
         if (GenericUtils.isEmpty(algorithm)) {
             return algorithm;


[mina-sshd] 05/05: Tighter timeout limit on wait time for SFTP server response to client message

Posted by lg...@apache.org.
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 4c19ef04fd2dfb35699da39abe04111dbcdaa4ff
Author: Lyor Goldstein <lg...@apache.org>
AuthorDate: Fri May 22 12:30:38 2020 +0300

    Tighter timeout limit on wait time for SFTP server response to client message
---
 .../common/util/closeable/SequentialCloseable.java |  4 +-
 .../common/util/closeable/SimpleCloseable.java     |  5 +++
 .../subsystem/sftp/impl/DefaultSftpClient.java     | 15 ++++++-
 .../client/subsystem/sftp/SftpTransferTest.java    | 50 ++++++++++++----------
 4 files changed, 48 insertions(+), 26 deletions(-)

diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/closeable/SequentialCloseable.java b/sshd-common/src/main/java/org/apache/sshd/common/util/closeable/SequentialCloseable.java
index 10f18b7..2d4a65a 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/util/closeable/SequentialCloseable.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/closeable/SequentialCloseable.java
@@ -51,7 +51,7 @@ public class SequentialCloseable extends SimpleCloseable {
                     Closeable c = iterator.next();
                     if (c != null) {
                         if (traceEnabled) {
-                            log.trace("doClose(" + immediately + ") closing " + c);
+                            log.trace("doClose({}) closing {} immediately={}", this, c, immediately);
                         }
                         CloseFuture nextFuture = c.close(immediately);
                         nextFuture.addListener(this);
@@ -60,7 +60,7 @@ public class SequentialCloseable extends SimpleCloseable {
                 }
                 if (!iterator.hasNext()) {
                     if (log.isDebugEnabled()) {
-                        log.debug("doClose(" + immediately + ") signal close complete");
+                        log.debug("doClose({}) signal close complete immediately={}", this, immediately);
                     }
                     future.setClosed();
                 }
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/closeable/SimpleCloseable.java b/sshd-common/src/main/java/org/apache/sshd/common/util/closeable/SimpleCloseable.java
index f232251..c147f72 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/util/closeable/SimpleCloseable.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/closeable/SimpleCloseable.java
@@ -68,4 +68,9 @@ public class SimpleCloseable extends IoBaseCloseable {
     protected void doClose(boolean immediately) {
         future.setClosed();
     }
+
+    @Override
+    public String toString() {
+        return getClass().getSimpleName() + "[" + future + "]";
+    }
 }
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/impl/DefaultSftpClient.java b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/impl/DefaultSftpClient.java
index 7070c8e..16c5f81 100644
--- a/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/impl/DefaultSftpClient.java
+++ b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/impl/DefaultSftpClient.java
@@ -309,13 +309,26 @@ public class DefaultSftpClient extends AbstractSftpClient {
                 throw new SshException("Channel is being closed");
             }
 
+            long rcvStart = System.nanoTime();
             Buffer buffer = receive(id, idleTimeout);
+            long rcvEnd = System.nanoTime();
             if (buffer != null) {
                 return buffer;
             }
 
+            long rcvDuration = TimeUnit.NANOSECONDS.toMillis(rcvEnd - rcvStart);
+            if (rcvDuration <= 0L) {
+                idleTimeout--;
+            } else {
+                idleTimeout -= rcvDuration;
+            }
+
+            if (idleTimeout <= 0L) {
+                throw new SshException("Timeout expired while waiting for id=" + id);
+            }
+
             if (traceEnabled) {
-                log.trace("receive({}) check iteration #{} for id={}", this, count, id);
+                log.trace("receive({}) check iteration #{} for id={} remain time={}", this, count, id, idleTimeout);
             }
         }
     }
diff --git a/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpTransferTest.java b/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpTransferTest.java
index 8653ee0..05f94b4 100644
--- a/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpTransferTest.java
+++ b/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpTransferTest.java
@@ -44,32 +44,36 @@ public class SftpTransferTest extends AbstractSftpClientTestSupport {
 
     @Test
     public void testTransferIntegrity() throws IOException {
+        Path localRoot = detectTargetFolder().resolve("sftp");
+        Files.createDirectories(localRoot);
+
+        Path local0 = localRoot.resolve("files-0.txt");
+        Files.deleteIfExists(local0);
+
+        String data = getClass().getName() + "#" + getCurrentTestName() + "(" + new Date() + ")" + System.lineSeparator();
+        try (BufferedWriter bos = Files.newBufferedWriter(local0)) {
+            long count = 0L;
+            while (count < 1024L * 1024L * 10L) { // 10 MB
+                bos.append(data);
+                count += data.length();
+            }
+        }
+
         try (ClientSession session = createClientSession();
              SftpFileSystem fs = SftpClientFactory.instance().createSftpFileSystem(session)) {
 
-            Path localRoot = detectTargetFolder().resolve("sftp");
             Path remoteRoot = fs.getDefaultDir().resolve("target/sftp");
-
-            Path local0 = localRoot.resolve("files-0.txt");
             Path remote0 = remoteRoot.resolve("files-1.txt");
-            Path local1 = localRoot.resolve("files-2.txt");
-            Path remote1 = remoteRoot.resolve("files-3.txt");
-            Path local2 = localRoot.resolve("files-4.txt");
-            Files.deleteIfExists(local0);
             Files.deleteIfExists(remote0);
+
+            Path local1 = localRoot.resolve("files-2.txt");
             Files.deleteIfExists(local1);
+
+            Path remote1 = remoteRoot.resolve("files-3.txt");
             Files.deleteIfExists(remote1);
-            Files.deleteIfExists(local2);
 
-            Files.createDirectories(localRoot);
-            String data = getClass().getName() + "#" + getCurrentTestName() + "(" + new Date() + ")\n";
-            try (BufferedWriter bos = Files.newBufferedWriter(local0)) {
-                long count = 0;
-                while (count < 1024 * 1024 * 10) { // 10 MB
-                    bos.append(data);
-                    count += data.length();
-                }
-            }
+            Path local2 = localRoot.resolve("files-4.txt");
+            Files.deleteIfExists(local2);
 
             Files.copy(local0, remote0);
             Files.copy(remote0, local1);
@@ -93,11 +97,12 @@ public class SftpTransferTest extends AbstractSftpClientTestSupport {
         }
     }
 
-    private boolean sameContent(Path path, Path path2) throws IOException {
-        byte[] buffer1 = new byte[BUFFER_SIZE];
-        byte[] buffer2 = new byte[BUFFER_SIZE];
+    private static boolean sameContent(Path path, Path path2) throws IOException {
         try (InputStream in1 = Files.newInputStream(path);
              InputStream in2 = Files.newInputStream(path2)) {
+            byte[] buffer1 = new byte[BUFFER_SIZE];
+            byte[] buffer2 = new byte[BUFFER_SIZE];
+
             while (true) {
                 int nRead1 = readNBytes(in1, buffer1);
                 int nRead2 = readNBytes(in2, buffer2);
@@ -119,17 +124,16 @@ public class SftpTransferTest extends AbstractSftpClientTestSupport {
         }
     }
 
-    private int readNBytes(InputStream is, byte[] b) throws IOException {
+    private static int readNBytes(InputStream is, byte[] b) throws IOException {
         int n = 0;
         int len = b.length;
         while (n < len) {
             int count = is.read(b, n, len - n);
             if (count < 0) {
-                break;
+                return n;
             }
             n += count;
         }
         return n;
     }
-
 }


[mina-sshd] 01/05: Fixed some code format and definition issues

Posted by lg...@apache.org.
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 cea78ef4b8cc35dedfddaa2924e8eeafe70e86ba
Author: Lyor Goldstein <lg...@apache.org>
AuthorDate: Fri May 1 10:54:57 2020 +0300

    Fixed some code format and definition issues
---
 README.md                                          |  2 +
 .../keys/impl/OpenSSHCertificateDecoder.java       |  6 +-
 .../loader/pem/DSSPEMResourceKeyPairParser.java    |  8 +--
 .../loader/pem/ECDSAPEMResourceKeyPairParser.java  | 72 ++++++++++++++++++----
 .../keys/loader/pem/PEMResourceParserUtils.java    |  4 ++
 .../loader/pem/RSAPEMResourceKeyPairParser.java    |  8 +--
 .../signature/AbstractSecurityKeySignature.java    |  6 +-
 .../org/apache/sshd/common/util/GenericUtils.java  |  7 ++-
 8 files changed, 80 insertions(+), 33 deletions(-)

diff --git a/README.md b/README.md
index 2a67a34..e3dc9ed 100644
--- a/README.md
+++ b/README.md
@@ -19,8 +19,10 @@ based applications requiring SSH support.
 * [RFC 4345 - Improved Arcfour Modes for the Secure Shell (SSH) Transport Layer Protocol](https://tools.ietf.org/html/rfc4345)
 * [RFC 4419 - Diffie-Hellman Group Exchange for the Secure Shell (SSH) Transport Layer Protocol](https://tools.ietf.org/html/rfc4419)
 * [RFC 4716 - The Secure Shell (SSH) Public Key File Format](https://tools.ietf.org/html/rfc4716)
+* [RFC 5208 - Public-Key Cryptography Standards (PKCS) #8 - version 1.2](https://tools.ietf.org/html/rfc5208)
 * [RFC 5480 - Elliptic Curve Cryptography Subject Public Key Information](https://tools.ietf.org/html/rfc5480)
 * [RFC 5656 - Elliptic Curve Algorithm Integration in the Secure Shell Transport Layer](https://tools.ietf.org/html/rfc5656)
+* [RFC 5915 - Elliptic Curve Private Key Structure](https://tools.ietf.org/html/rfc5915)
 * [RFC 6668 - SHA-2 Data Integrity Verification for the Secure Shell (SSH) Transport Layer Protocol](https://tools.ietf.org/html/rfc6668)
 * [RFC 8160 - IUTF8 Terminal Mode in Secure Shell (SSH)](https://tools.ietf.org/html/rfc8160)
 * [RFC 8268 - More Modular Exponentiation (MODP) Diffie-Hellman (DH) Key Exchange (KEX) Groups for Secure Shell (SSH)](https://tools.ietf.org/html/rfc8268)
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/OpenSSHCertificateDecoder.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/OpenSSHCertificateDecoder.java
index f73c64f..d8d947f 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/OpenSSHCertificateDecoder.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/OpenSSHCertificateDecoder.java
@@ -63,13 +63,9 @@ public class OpenSSHCertificateDecoder extends AbstractPublicKeyEntryDecoder<Ope
     public OpenSshCertificate decodePublicKey(
             SessionContext session, String keyType, InputStream keyData, Map<String, String> headers)
             throws IOException, GeneralSecurityException {
-
         byte[] bytes = IoUtils.toByteArray(keyData);
-
         ByteArrayBuffer buffer = new ByteArrayBuffer(bytes);
-
-        OpenSshCertificate cert = (OpenSshCertificate) OpenSSHCertPublicKeyParser.INSTANCE.getRawPublicKey(keyType, buffer);
-
+        OpenSshCertificate cert = OpenSSHCertPublicKeyParser.INSTANCE.getRawPublicKey(keyType, buffer);
         if (cert.getType() != OpenSshCertificate.SSH_CERT_TYPE_HOST) {
             throw new GeneralSecurityException("The provided certificate is not a Host certificate.");
         }
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
index f1b6b77..ca4f04a 100644
--- 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
@@ -47,6 +47,7 @@ import org.apache.sshd.common.util.security.SecurityUtils;
 
 /**
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ * @see <a href="https://tools.ietf.org/html/rfc3279#section-2.3.2">RFC-3279 section 2.3.2</a>
  */
 public class DSSPEMResourceKeyPairParser extends AbstractPEMResourceKeyPairParser {
     // Not exactly according to standard but good enough
@@ -56,9 +57,6 @@ public class DSSPEMResourceKeyPairParser extends AbstractPEMResourceKeyPairParse
     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();
@@ -82,7 +80,7 @@ public class DSSPEMResourceKeyPairParser extends AbstractPEMResourceKeyPairParse
      * <p>
      * The ASN.1 syntax for the private key:
      * </P>
-     * 
+     *
      * <pre>
      * <code>
      * DSAPrivateKey ::= SEQUENCE {
@@ -95,7 +93,7 @@ public class DSSPEMResourceKeyPairParser extends AbstractPEMResourceKeyPairParse
      * }
      * </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
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 907efba..12b74f9 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
@@ -51,6 +51,7 @@ import org.apache.sshd.common.util.security.SecurityUtils;
 
 /**
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ * @see <a href="https://tools.ietf.org/html/rfc5915">RFC 5915</a>
  */
 public class ECDSAPEMResourceKeyPairParser extends AbstractPEMResourceKeyPairParser {
     public static final String BEGIN_MARKER = "BEGIN EC PRIVATE KEY";
@@ -91,10 +92,10 @@ public class ECDSAPEMResourceKeyPairParser extends AbstractPEMResourceKeyPairPar
 
     /**
      * <P>
-     * ASN.1 syntax according to rfc5915 is:
+     * ASN.1 syntax according to <A HREF="https://tools.ietf.org/html/rfc5915">RFC 5915</A> is:
      * </P>
      * </BR>
-     * 
+     *
      * <PRE>
      * <CODE>
      * ECPrivateKey ::= SEQUENCE {
@@ -109,7 +110,7 @@ public class ECDSAPEMResourceKeyPairParser extends AbstractPEMResourceKeyPairPar
      * <I>ECParameters</I> syntax according to RFC5480:
      * </P>
      * </BR>
-     * 
+     *
      * <PRE>
      * <CODE>
      * ECParameters ::= CHOICE {
@@ -119,7 +120,7 @@ public class ECDSAPEMResourceKeyPairParser extends AbstractPEMResourceKeyPairPar
      * }
      * </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
@@ -134,11 +135,16 @@ public class ECDSAPEMResourceKeyPairParser extends AbstractPEMResourceKeyPairPar
             sequence = parser.readObject();
         }
 
-        if (!ASN1Type.SEQUENCE.equals(sequence.getObjType())) {
-            throw new IOException("Invalid DER: not a sequence: " + sequence.getObjType());
+        return decodeECPrivateKeySpec(sequence);
+    }
+
+    public static SimpleImmutableEntry<ECPublicKeySpec, ECPrivateKeySpec> decodeECPrivateKeySpec(ASN1Object sequence)
+            throws IOException {
+        ASN1Type objType = (sequence == null) ? null : sequence.getObjType();
+        if (!ASN1Type.SEQUENCE.equals(objType)) {
+            throw new IOException("Invalid DER: not a sequence: " + objType);
         }
 
-        // Parse inside the sequence
         try (DERParser parser = sequence.createParser()) {
             ECPrivateKeySpec prvSpec = decodeECPrivateKeySpec(parser);
             ECCurves curve = ECCurves.fromCurveParameters(prvSpec.getParams());
@@ -146,20 +152,45 @@ public class ECDSAPEMResourceKeyPairParser extends AbstractPEMResourceKeyPairPar
                 throw new StreamCorruptedException("Unknown curve");
             }
 
+            /*
+             * According to https://tools.ietf.org/html/rfc5915 - section 3
+             *
+             * ECPrivateKey ::= SEQUENCE {
+             *      version        INTEGER { ecPrivkeyVer1(1) } (ecPrivkeyVer1),
+             *      privateKey     OCTET STRING,
+             *      parameters [0] ECParameters {{ NamedCurve }} OPTIONAL,
+             *      publicKey  [1] BIT STRING OPTIONAL
+             * }
+             */
             ECPoint w = decodeECPublicKeyValue(curve, parser);
             ECPublicKeySpec pubSpec = new ECPublicKeySpec(w, prvSpec.getParams());
             return new SimpleImmutableEntry<>(pubSpec, prvSpec);
         }
     }
 
+    /*
+     * According to https://tools.ietf.org/html/rfc5915 - section 3
+     *
+     * ECPrivateKey ::= SEQUENCE {
+     *      version        INTEGER { ecPrivkeyVer1(1) } (ecPrivkeyVer1),
+     *      privateKey     OCTET STRING,
+     *      parameters [0] ECParameters {{ NamedCurve }} OPTIONAL,
+     *      publicKey  [1] BIT STRING OPTIONAL
+     * }
+     */
     public static final ECPrivateKeySpec decodeECPrivateKeySpec(DERParser parser) throws IOException {
         // see openssl asn1parse -inform PEM -in ...file... -dump
-        ASN1Object versionObject = parser.readObject(); // Skip version
+        ASN1Object versionObject = parser.readObject();
         if (versionObject == null) {
             throw new StreamCorruptedException("No version");
         }
 
-        // as per RFC-5915 section 3
+        /*
+         * According to https://tools.ietf.org/html/rfc5915 - section 3
+         *
+         *      For this version of the document, it SHALL be set to ecPrivkeyVer1,
+         *      which is of type INTEGER and whose value is one (1)
+         */
         BigInteger version = versionObject.asInteger();
         if (!BigInteger.ONE.equals(version)) {
             throw new StreamCorruptedException("Bad version value: " + version);
@@ -175,6 +206,18 @@ public class ECDSAPEMResourceKeyPairParser extends AbstractPEMResourceKeyPairPar
             throw new StreamCorruptedException("Non-matching private key object type: " + objType);
         }
 
+        /*
+         * According to https://tools.ietf.org/html/rfc5915 - section 3
+         *
+         *      parameters specifies the elliptic curve domain parameters
+         *      associated to the private key.  The type ECParameters is discussed
+         *      in [RFC5480].  As specified in [RFC5480], only the namedCurve
+         *      CHOICE is permitted.  namedCurve is an object identifier that
+         *      fully identifies the required values for a particular set of
+         *      elliptic 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();
         if (paramsObject == null) {
             throw new StreamCorruptedException("No parameters value");
@@ -206,13 +249,13 @@ public class ECDSAPEMResourceKeyPairParser extends AbstractPEMResourceKeyPairPar
      * ASN.1 syntax according to rfc5915 is:
      * </P>
      * </BR>
-     * 
+     *
      * <pre>
      * <code>
      *      publicKey  [1] BIT STRING OPTIONAL
      * </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}
@@ -225,6 +268,13 @@ public class ECDSAPEMResourceKeyPairParser extends AbstractPEMResourceKeyPairPar
             throw new StreamCorruptedException("No public key data bytes");
         }
 
+        /*
+         * 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
+         */
         try (DERParser dataParser = dataObject.createParser()) {
             ASN1Object pointData = dataParser.readObject();
             if (pointData == null) {
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
index 3b28890..df7a72f 100644
--- 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
@@ -91,6 +91,10 @@ public final class PEMResourceParserUtils {
         }
     }
 
+    public static KeyPairPEMResourceParser getPEMResourceParserByOidValues(Collection<? extends Number> oid) {
+        return getPEMResourceParserByOid(GenericUtils.join(oid, '.'));
+    }
+
     public static KeyPairPEMResourceParser getPEMResourceParserByOid(String oid) {
         if (GenericUtils.isEmpty(oid)) {
             return null;
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
index f091dba..d599afe 100644
--- 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
@@ -48,6 +48,7 @@ import org.apache.sshd.common.util.security.SecurityUtils;
 
 /**
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ * @see <a href="https://tools.ietf.org/html/rfc3279#section-2.3.1">RFC-3279 section 2.3.1</a>
  */
 public class RSAPEMResourceKeyPairParser extends AbstractPEMResourceKeyPairParser {
     // Not exactly according to standard but good enough
@@ -57,9 +58,6 @@ public class RSAPEMResourceKeyPairParser extends AbstractPEMResourceKeyPairParse
     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();
@@ -83,7 +81,7 @@ public class RSAPEMResourceKeyPairParser extends AbstractPEMResourceKeyPairParse
      * <p>
      * The ASN.1 syntax for the private key as per RFC-3447 section A.1.2:
      * </P>
-     * 
+     *
      * <pre>
      * <code>
      * RSAPrivateKey ::= SEQUENCE {
@@ -100,7 +98,7 @@ public class RSAPEMResourceKeyPairParser extends AbstractPEMResourceKeyPairParse
      * }
      * </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
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/signature/AbstractSecurityKeySignature.java b/sshd-common/src/main/java/org/apache/sshd/common/signature/AbstractSecurityKeySignature.java
index 5f50c1d..8745656 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/signature/AbstractSecurityKeySignature.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/signature/AbstractSecurityKeySignature.java
@@ -30,12 +30,10 @@ import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
 import org.apache.sshd.common.util.security.SecurityUtils;
 
 public abstract class AbstractSecurityKeySignature implements Signature {
-
     private static final int FLAG_USER_PRESENCE = 0x01;
 
     private final String keyType;
-
-    private SecurityKeyPublicKey publicKey;
+    private SecurityKeyPublicKey<?> publicKey;
     private MessageDigest challengeDigest;
 
     protected AbstractSecurityKeySignature(String keyType) {
@@ -47,7 +45,7 @@ public abstract class AbstractSecurityKeySignature implements Signature {
         if (!(key instanceof SecurityKeyPublicKey)) {
             throw new IllegalArgumentException("Only instances of SecurityKeyPublicKey can be used");
         }
-        this.publicKey = (SecurityKeyPublicKey) key;
+        this.publicKey = (SecurityKeyPublicKey<?>) key;
         this.challengeDigest = SecurityUtils.getMessageDigest("SHA-256");
     }
 
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/GenericUtils.java b/sshd-common/src/main/java/org/apache/sshd/common/util/GenericUtils.java
index e5da0d5..4ed0514 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/util/GenericUtils.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/GenericUtils.java
@@ -873,7 +873,7 @@ public final class GenericUtils {
 
     /**
      * Wraps a value into a {@link Supplier}
-     * 
+     *
      * @param  <T>   Type of value being supplied
      * @param  value The value to be supplied
      * @return       The supplier wrapper
@@ -1014,11 +1014,12 @@ public final class GenericUtils {
 
     /**
      * The delegate Suppliers get() method is called exactly once and the result is cached.
-     * 
+     *
+     * @param <T> Generic type of supplied value
      * @param  delegate The actual Supplier
      * @return          The memoized Supplier
      */
-    public static <T> Supplier<T> memoizeLock(Supplier<T> delegate) {
+    public static <T> Supplier<T> memoizeLock(Supplier<? extends T> delegate) {
         AtomicReference<T> value = new AtomicReference<>();
         return () -> {
             T val = value.get();