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 17:48:02 UTC
[mina-sshd] branch master updated: [SSHD-989] Add support for
parsing PKCS8 encoded ed25519 private key
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
The following commit(s) were added to refs/heads/master by this push:
new d1b5beb [SSHD-989] Add support for parsing PKCS8 encoded ed25519 private key
d1b5beb is described below
commit d1b5bebaa9c531bfea5cf4905ab14a85a9f2d403
Author: Lyor Goldstein <lg...@apache.org>
AuthorDate: Fri May 22 20:41:13 2020 +0300
[SSHD-989] Add support for parsing PKCS8 encoded ed25519 private key
---
.../loader/pem/PKCS8PEMResourceKeyPairParser.java | 5 +
.../eddsa/Ed25519PEMResourceKeyParser.java | 183 +++++++++++++++++++++
.../pem/PKCS8PEMResourceKeyPairParserTest.java | 11 +-
.../common/config/keys/loader/pem/pkcs8-eddsa.pem | 3 +
4 files changed, 199 insertions(+), 3 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 3207c38..2ef18aa 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
@@ -45,6 +45,7 @@ import org.apache.sshd.common.util.io.der.ASN1Object;
import org.apache.sshd.common.util.io.der.ASN1Type;
import org.apache.sshd.common.util.io.der.DERParser;
import org.apache.sshd.common.util.security.SecurityUtils;
+import org.apache.sshd.common.util.security.eddsa.Ed25519PEMResourceKeyParser;
/**
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
@@ -107,6 +108,10 @@ public class PKCS8PEMResourceKeyPairParser extends AbstractPEMResourceKeyPairPar
try (DERParser parser = privateKeyBytes.createParser()) {
kp = ECDSAPEMResourceKeyPairParser.parseECKeyPair(curve, parser);
}
+ } else if (SecurityUtils.isEDDSACurveSupported()
+ && Ed25519PEMResourceKeyParser.ED25519_OID.endsWith(oid)) {
+ ASN1Object privateKeyBytes = pkcs8Info.getPrivateKeyBytes();
+ kp = Ed25519PEMResourceKeyParser.decodeEd25519KeyPair(privateKeyBytes.getPureValueBytes());
} else {
PrivateKey prvKey = decodePEMPrivateKeyPKCS8(oidAlgorithm, encBytes);
PublicKey pubKey = ValidateUtils.checkNotNull(KeyUtils.recoverPublicKey(prvKey),
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/Ed25519PEMResourceKeyParser.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/Ed25519PEMResourceKeyParser.java
new file mode 100644
index 0000000..b658340
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/Ed25519PEMResourceKeyParser.java
@@ -0,0 +1,183 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sshd.common.util.security.eddsa;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.StreamCorruptedException;
+import java.math.BigInteger;
+import java.security.GeneralSecurityException;
+import java.security.KeyFactory;
+import java.security.KeyPair;
+import java.security.NoSuchAlgorithmException;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import net.i2p.crypto.eddsa.EdDSAKey;
+import net.i2p.crypto.eddsa.EdDSAPrivateKey;
+import net.i2p.crypto.eddsa.EdDSAPublicKey;
+import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable;
+import net.i2p.crypto.eddsa.spec.EdDSAParameterSpec;
+import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec;
+import org.apache.sshd.common.NamedResource;
+import org.apache.sshd.common.config.keys.FilePasswordProvider;
+import org.apache.sshd.common.config.keys.loader.pem.AbstractPEMResourceKeyPairParser;
+import org.apache.sshd.common.session.SessionContext;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.io.NoCloseInputStream;
+import org.apache.sshd.common.util.io.der.ASN1Object;
+import org.apache.sshd.common.util.io.der.ASN1Type;
+import org.apache.sshd.common.util.io.der.DERParser;
+import org.apache.sshd.common.util.security.SecurityUtils;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class Ed25519PEMResourceKeyParser extends AbstractPEMResourceKeyPairParser {
+ // TODO find out how the markers really look like for now provide something
+ public static final String BEGIN_MARKER = "BEGIN EDDSA PRIVATE KEY";
+ public static final List<String> BEGINNERS = Collections.unmodifiableList(Collections.singletonList(BEGIN_MARKER));
+
+ public static final String END_MARKER = "END EDDSA PRIVATE KEY";
+ public static final List<String> ENDERS = Collections.unmodifiableList(Collections.singletonList(END_MARKER));
+
+ /**
+ * @see <A HREF="https://tools.ietf.org/html/rfc8410#section-3>RFC8412 section 3</A>
+ */
+ public static final String ED25519_OID = "1.3.101.112";
+
+ public static final Ed25519PEMResourceKeyParser INSTANCE = new Ed25519PEMResourceKeyParser();
+
+ public Ed25519PEMResourceKeyParser() {
+ super(EdDSAKey.KEY_ALGORITHM, ED25519_OID, BEGINNERS, ENDERS);
+ }
+
+ @Override
+ public Collection<KeyPair> extractKeyPairs(
+ SessionContext session, NamedResource resourceKey, String beginMarker,
+ String endMarker, FilePasswordProvider passwordProvider,
+ InputStream stream, Map<String, String> headers)
+ throws IOException, GeneralSecurityException {
+ KeyPair kp = parseEd25519KeyPair(stream, false);
+ return Collections.singletonList(kp);
+ }
+
+ public static KeyPair parseEd25519KeyPair(
+ InputStream inputStream, boolean okToClose)
+ throws IOException, GeneralSecurityException {
+ try (DERParser parser = new DERParser(NoCloseInputStream.resolveInputStream(inputStream, okToClose))) {
+ return parseEd25519KeyPair(parser);
+ }
+ }
+
+ /*
+ * See https://tools.ietf.org/html/rfc8410#section-7
+ *
+ * SEQUENCE {
+ * INTEGER 0x00 (0 decimal)
+ * SEQUENCE {
+ * OBJECTIDENTIFIER 1.3.101.112
+ * }
+ * OCTETSTRING keyData
+ * }
+ *
+ * NOTE: there is another variant that also has some extra parameters
+ * but it has the same "prefix" structure so we don't care
+ */
+ public static KeyPair parseEd25519KeyPair(DERParser parser) throws IOException, GeneralSecurityException {
+ ASN1Object obj = parser.readObject();
+ if (obj == null) {
+ throw new StreamCorruptedException("Missing version value");
+ }
+
+ BigInteger version = obj.asInteger();
+ if (!BigInteger.ZERO.equals(version)) {
+ throw new StreamCorruptedException("Invalid version: " + version);
+ }
+
+ obj = parser.readObject();
+ if (obj == null) {
+ throw new StreamCorruptedException("Missing OID container");
+ }
+
+ ASN1Type objType = obj.getObjType();
+ if (objType != ASN1Type.SEQUENCE) {
+ throw new StreamCorruptedException("Unexpected OID object type: " + objType);
+ }
+
+ List<Integer> curveOid;
+ try (DERParser oidParser = obj.createParser()) {
+ obj = oidParser.readObject();
+ if (obj == null) {
+ throw new StreamCorruptedException("Missing OID value");
+ }
+
+ curveOid = obj.asOID();
+ }
+
+ String oid = GenericUtils.join(curveOid, '.');
+ // TODO modify if more curves supported
+ if (!ED25519_OID.equals(oid)) {
+ throw new StreamCorruptedException("Unsupported curve OID: " + oid);
+ }
+
+ obj = parser.readObject();
+ if (obj == null) {
+ throw new StreamCorruptedException("Missing key data");
+ }
+
+ return decodeEd25519KeyPair(obj.getValue());
+ }
+
+ public static KeyPair decodeEd25519KeyPair(byte[] keyData) throws IOException, GeneralSecurityException {
+ EdDSAPrivateKey privateKey = decodeEdDSAPrivateKey(keyData);
+ EdDSAPublicKey publicKey = EdDSASecurityProviderUtils.recoverEDDSAPublicKey(privateKey);
+ return new KeyPair(publicKey, privateKey);
+ }
+
+ public static EdDSAPrivateKey decodeEdDSAPrivateKey(byte[] keyData) throws IOException, GeneralSecurityException {
+ try (DERParser parser = new DERParser(keyData)) {
+ ASN1Object obj = parser.readObject();
+ if (obj == null) {
+ throw new StreamCorruptedException("Missing key data container");
+ }
+
+ ASN1Type objType = obj.getObjType();
+ if (objType != ASN1Type.OCTET_STRING) {
+ throw new StreamCorruptedException("Mismatched key data container type: " + objType);
+ }
+
+ return generateEdDSAPrivateKey(obj.getValue());
+ }
+ }
+
+ public static EdDSAPrivateKey generateEdDSAPrivateKey(byte[] seed) throws GeneralSecurityException {
+ if (!SecurityUtils.isEDDSACurveSupported()) {
+ throw new NoSuchAlgorithmException(SecurityUtils.EDDSA + " provider not supported");
+ }
+
+ EdDSAParameterSpec params = EdDSANamedCurveTable.getByName(EdDSASecurityProviderUtils.CURVE_ED25519_SHA512);
+ EdDSAPrivateKeySpec keySpec = new EdDSAPrivateKeySpec(seed, params);
+ KeyFactory factory = SecurityUtils.getKeyFactory(SecurityUtils.EDDSA);
+ return EdDSAPrivateKey.class.cast(factory.generatePrivate(keySpec));
+ }
+}
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 76b9224..ab8cf8c 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
@@ -68,7 +68,7 @@ public class PKCS8PEMResourceKeyPairParserTest extends JUnitTestSupport {
this.keySize = keySize;
}
- @Parameters(name = "{0} / {1}")
+ @Parameters(name = "{0}-{1}")
public static List<Object[]> parameters() {
List<Object[]> params = new ArrayList<>();
for (Integer ks : RSA_SIZES) {
@@ -87,6 +87,9 @@ public class PKCS8PEMResourceKeyPairParserTest extends JUnitTestSupport {
params.add(new Object[] { KeyUtils.EC_ALGORITHM, curve.getKeySize() });
}
}
+ if (SecurityUtils.isEDDSACurveSupported()) {
+ params.add(new Object[] { SecurityUtils.EDDSA, 0 });
+ }
return params;
}
@@ -96,8 +99,8 @@ public class PKCS8PEMResourceKeyPairParserTest extends JUnitTestSupport {
if (keySize > 0) {
generator.initialize(keySize);
}
- KeyPair kp = generator.generateKeyPair();
+ KeyPair kp = generator.generateKeyPair();
try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
Collection<Object> items = new ArrayList<>();
PrivateKey prv1 = kp.getPrivate();
@@ -126,11 +129,13 @@ public class PKCS8PEMResourceKeyPairParserTest extends JUnitTestSupport {
* 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 ed25519 -out pkcs8-ed25519.pem
* openssl asn1parse -inform PEM -in ...file... -dump
*/
@Test // see SSHD-989
public void testPKCS8FileParsing() throws Exception {
- String resourceKey = "pkcs8-" + algorithm.toLowerCase() + "-" + keySize + ".pem";
+ String baseName = "pkcs8-" + algorithm.toLowerCase();
+ String resourceKey = baseName + ((keySize > 0) ? "-" + keySize : "") + ".pem";
URL url = getClass().getResource(resourceKey);
Assume.assumeTrue("No test file=" + resourceKey, url != null);
diff --git a/sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/pem/pkcs8-eddsa.pem b/sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/pem/pkcs8-eddsa.pem
new file mode 100644
index 0000000..c8e2f2e
--- /dev/null
+++ b/sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/pem/pkcs8-eddsa.pem
@@ -0,0 +1,3 @@
+-----BEGIN PRIVATE KEY-----
+MC4CAQAwBQYDK2VwBCIEIB5k1Srs4YLjjoqR05Nu9CeBMVA2CDGK37sqjIoTehL1
+-----END PRIVATE KEY-----