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:38 UTC
[mina-sshd] 02/05: [SSHD-989] Moved code that parses PKCS8 encoding
to dedicated class
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) {