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 <lgoldst...@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:d...@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:d...@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) {