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) {