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:39 UTC
[mina-sshd] 03/05: [SSHD-989] Implement ECDSA public key recovery
from PKCS#8 encoded data
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) {