diff --git a/src/java.base/share/classes/java/security/AsymmetricKey.java b/src/java.base/share/classes/java/security/AsymmetricKey.java index e96aeb4d84c71..d37afe9bfea89 100644 --- a/src/java.base/share/classes/java/security/AsymmetricKey.java +++ b/src/java.base/share/classes/java/security/AsymmetricKey.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -34,7 +34,7 @@ * * @since 22 */ -public interface AsymmetricKey extends Key { +public non-sealed interface AsymmetricKey extends Key, DEREncodable { /** * Returns the parameters associated with this key. * The parameters are optional and may be either diff --git a/src/java.base/share/classes/java/security/DEREncodable.java b/src/java.base/share/classes/java/security/DEREncodable.java new file mode 100644 index 0000000000000..63c6a73ee52b5 --- /dev/null +++ b/src/java.base/share/classes/java/security/DEREncodable.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.security; + +import jdk.internal.javac.PreviewFeature; + +import javax.crypto.EncryptedPrivateKeyInfo; +import java.security.cert.X509CRL; +import java.security.cert.X509Certificate; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; + +/** + * This interface is implemented by security API classes that contain + * binary-encodable key or certificate material. + * These APIs or their subclasses typically provide methods to convert + * their instances to and from byte arrays in the Distinguished + * Encoding Rules (DER) format. + * + * @see AsymmetricKey + * @see KeyPair + * @see PKCS8EncodedKeySpec + * @see X509EncodedKeySpec + * @see EncryptedPrivateKeyInfo + * @see X509Certificate + * @see X509CRL + * @see PEMRecord + * + * @since 25 + */ + +@PreviewFeature(feature = PreviewFeature.Feature.PEM_API) +public sealed interface DEREncodable permits AsymmetricKey, KeyPair, + PKCS8EncodedKeySpec, X509EncodedKeySpec, EncryptedPrivateKeyInfo, + X509Certificate, X509CRL, PEMRecord { +} diff --git a/src/java.base/share/classes/java/security/KeyPair.java b/src/java.base/share/classes/java/security/KeyPair.java index cc648a677dd5a..39c98501fea9d 100644 --- a/src/java.base/share/classes/java/security/KeyPair.java +++ b/src/java.base/share/classes/java/security/KeyPair.java @@ -37,7 +37,7 @@ * @since 1.1 */ -public final class KeyPair implements java.io.Serializable { +public final class KeyPair implements java.io.Serializable, DEREncodable { @java.io.Serial private static final long serialVersionUID = -7565189502268009837L; diff --git a/src/java.base/share/classes/java/security/PEMDecoder.java b/src/java.base/share/classes/java/security/PEMDecoder.java new file mode 100644 index 0000000000000..eeb6decdf4078 --- /dev/null +++ b/src/java.base/share/classes/java/security/PEMDecoder.java @@ -0,0 +1,512 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.security; + +import jdk.internal.javac.PreviewFeature; + +import sun.security.pkcs.PKCS8Key; +import sun.security.rsa.RSAPrivateCrtKeyImpl; +import sun.security.util.KeyUtil; +import sun.security.util.Pem; + +import javax.crypto.EncryptedPrivateKeyInfo; +import javax.crypto.spec.PBEKeySpec; +import java.io.*; +import java.nio.charset.StandardCharsets; +import java.security.cert.*; +import java.security.spec.*; +import java.util.Base64; +import java.util.Objects; + +/** + * {@code PEMDecoder} implements a decoder for Privacy-Enhanced Mail (PEM) data. + * PEM is a textual encoding used to store and transfer security + * objects, such as asymmetric keys, certificates, and certificate revocation + * lists (CRLs). It is defined in RFC 1421 and RFC 7468. PEM consists of a + * Base64-formatted binary encoding enclosed by a type-identifying header + * and footer. + * + *
The {@linkplain #decode(String)} and {@linkplain #decode(InputStream)} + * methods return an instance of a class that matches the data + * type and implements {@link DEREncodable}. + * + *
The following lists the supported PEM types and the {@code DEREncodable} + * types that each are decoded as: + *
The {@code PublicKey} and {@code PrivateKey} types, an algorithm specific + * subclass is returned if the underlying algorithm is supported. For example an + * ECPublicKey and ECPrivateKey for Elliptic Curve keys. + * + *
If the PEM type does not have a corresponding class, + * {@code decode(String)} and {@code decode(InputStream)} will return a + * {@link PEMRecord}. + * + *
The {@linkplain #decode(String, Class)} and + * {@linkplain #decode(InputStream, Class)} methods take a Class parameter + * which determines the type of {@code DEREncodable} that is returned. These + * methods are useful when extracting or changing the return class. + * For example, if the PEM contains both public and private keys, the + * Class parameter can specify which to return. Use + * {@code PrivateKey.class} to return only the private key. + * If the Class parameter is set to {@code X509EncodedKeySpec.class}, the + * public key will be returned in that format. Any type of PEM data can be + * decoded into a {@code PEMRecord} by specifying {@code PEMRecord.class}. + * If the Class parameter doesn't match the PEM content, an + * {@code IllegalArgumentException} will be thrown. + * + *
A new {@code PEMDecoder} instance is created when configured + * with {@linkplain #withFactory(Provider)} and/or + * {@linkplain #withDecryption(char[])}. {@linkplain #withFactory(Provider)} + * configures the decoder to use only {@linkplain KeyFactory} and + * {@linkplain CertificateFactory} instances from the given {@code Provider}. + * {@link#withDecryption(char[])} configures the decoder to decrypt all + * encrypted private key PEM data using the given password. + * Configuring an instance for decryption does not prevent decoding with + * unencrypted PEM. Any encrypted PEM that fails decryption + * will throw a {@link RuntimeException}. When an encrypted private key PEM is + * used with a decoder not configured for decryption, an + * {@link EncryptedPrivateKeyInfo} object is returned. + * + *
This class is immutable and thread-safe. + * + *
Here is an example of decoding a {@code PrivateKey} object: + * {@snippet lang = java: + * PEMDecoder pd = PEMDecoder.of(); + * PrivateKey priKey = pd.decode(priKeyPEM, PrivateKey.class); + * } + * + *
Here is an example of a {@code PEMDecoder} configured with decryption + * and a factory provider: + * {@snippet lang = java: + * PEMDecoder pe = PEMDecoder.of().withDecryption(password). + * withFactory(provider); + * byte[] pemData = pe.decode(privKey); + * } + * + * @implNote An implementation may support other PEM types and + * {@code DEREncodables}. This implementation additionally supports PEM types: + * {@code X509 CERTIFICATE}, {@code X.509 CERTIFICATE}, {@code CRL}, + * and {@code RSA PRIVATE KEY}. + * + * @see PEMEncoder + * @see PEMRecord + * @see EncryptedPrivateKeyInfo + * + * @spec https://www.rfc-editor.org/info/rfc1421 + * RFC 1421: Privacy Enhancement for Internet Electronic Mail + * @spec https://www.rfc-editor.org/info/rfc7468 + * RFC 7468: Textual Encodings of PKIX, PKCS, and CMS Structures + * + * @since 25 + */ + +@PreviewFeature(feature = PreviewFeature.Feature.PEM_API) +public final class PEMDecoder { + private final Provider factory; + private final PBEKeySpec password; + + // Singleton instance for PEMDecoder + private final static PEMDecoder PEM_DECODER = new PEMDecoder(null, null); + + /** + * Creates an instance with a specific KeyFactory and/or password. + * @param withFactory KeyFactory provider + * @param withPassword char[] password for EncryptedPrivateKeyInfo + * decryption + */ + private PEMDecoder(Provider withFactory, PBEKeySpec withPassword) { + password = withPassword; + factory = withFactory; + } + + /** + * Returns an instance of {@code PEMDecoder}. + * + * @return a {@code PEMDecoder} instance + */ + public static PEMDecoder of() { + return PEM_DECODER; + } + + /** + * After the header, footer, and base64 have been separated, identify the + * header and footer and proceed with decoding the base64 for the + * appropriate type. + */ + private DEREncodable decode(PEMRecord pem) { + Base64.Decoder decoder = Base64.getMimeDecoder(); + + try { + return switch (pem.type()) { + case Pem.PUBLIC_KEY -> { + X509EncodedKeySpec spec = + new X509EncodedKeySpec(decoder.decode(pem.pem())); + yield getKeyFactory( + KeyUtil.getAlgorithm(spec.getEncoded())). + generatePublic(spec); + } + case Pem.PRIVATE_KEY -> { + PKCS8Key p8key = new PKCS8Key(decoder.decode(pem.pem())); + String algo = p8key.getAlgorithm(); + KeyFactory kf = getKeyFactory(algo); + DEREncodable d = kf.generatePrivate( + new PKCS8EncodedKeySpec(p8key.getEncoded(), algo)); + + // Look for a public key inside the pkcs8 encoding. + if (p8key.getPubKeyEncoded() != null) { + // Check if this is a OneAsymmetricKey encoding + X509EncodedKeySpec spec = new X509EncodedKeySpec( + p8key.getPubKeyEncoded(), algo); + yield new KeyPair(getKeyFactory(algo). + generatePublic(spec), (PrivateKey) d); + + } else if (d instanceof PKCS8Key p8 && + p8.getPubKeyEncoded() != null) { + // If the KeyFactory decoded an algorithm-specific + // encodings, look for the public key again. This + // happens with EC and SEC1-v2 encoding + X509EncodedKeySpec spec = new X509EncodedKeySpec( + p8.getPubKeyEncoded(), algo); + yield new KeyPair(getKeyFactory(algo). + generatePublic(spec), p8); + } else { + // No public key, return the private key. + yield d; + } + } + case Pem.ENCRYPTED_PRIVATE_KEY -> { + if (password == null) { + yield new EncryptedPrivateKeyInfo(decoder.decode( + pem.pem())); + } + yield new EncryptedPrivateKeyInfo(decoder.decode(pem.pem())). + getKey(password.getPassword()); + } + case Pem.CERTIFICATE, Pem.X509_CERTIFICATE, + Pem.X_509_CERTIFICATE -> { + CertificateFactory cf = getCertFactory("X509"); + yield (X509Certificate) cf.generateCertificate( + new ByteArrayInputStream(decoder.decode(pem.pem()))); + } + case Pem.X509_CRL, Pem.CRL -> { + CertificateFactory cf = getCertFactory("X509"); + yield (X509CRL) cf.generateCRL( + new ByteArrayInputStream(decoder.decode(pem.pem()))); + } + case Pem.RSA_PRIVATE_KEY -> { + KeyFactory kf = getKeyFactory("RSA"); + yield kf.generatePrivate( + RSAPrivateCrtKeyImpl.getKeySpec(decoder.decode( + pem.pem()))); + } + default -> pem; + }; + } catch (GeneralSecurityException | IOException e) { + throw new IllegalArgumentException(e); + } + } + + /** + * Decodes and returns a {@link DEREncodable} from the given {@code String}. + * + *
This method reads the {@code String} until PEM data is found + * or the end of the {@code String} is reached. If no PEM data is found, + * an {@code IllegalArgumentException} is thrown. + * + *
This method returns a Java API cryptographic object, + * such as a {@code PrivateKey}, if the PEM type is supported. + * Any non-PEM data preceding the PEM header is ignored by the decoder. + * Otherwise, a {@link PEMRecord} will be returned containing + * the type identifier and Base64-encoded data. + * Any non-PEM data preceding the PEM header will be stored in + * {@code leadingData}. + * + *
Input consumed by this method is read in as + * {@link java.nio.charset.StandardCharsets#UTF_8 UTF-8}. + * + * @param str a String containing PEM data + * @return a {@code DEREncodable} + * @throws IllegalArgumentException on error in decoding or no PEM data + * found + * @throws NullPointerException when {@code str} is null + */ + public DEREncodable decode(String str) { + Objects.requireNonNull(str); + DEREncodable de; + try { + return decode(new ByteArrayInputStream( + str.getBytes(StandardCharsets.UTF_8))); + } catch (IOException e) { + // With all data contained in the String, there are no IO ops. + throw new IllegalArgumentException(e); + } + } + + /** + * Decodes and returns a {@link DEREncodable} from the given + * {@code InputStream}. + * + *
This method reads from the {@code InputStream} until the end of + * the PEM footer or the end of the stream. If an I/O error occurs, + * the read position in the stream may become inconsistent. + * It is recommended to perform no further decoding operations + * on the {@code InputStream}. + * + *
This method returns a Java API cryptographic object, + * such as a {@code PrivateKey}, if the PEM type is supported. + * Any non-PEM data preceding the PEM header is ignored by the decoder. + * Otherwise, a {@link PEMRecord} will be returned containing + * the type identifier and Base64-encoded data. + * Any non-PEM data preceding the PEM header will be stored in + * {@code leadingData}. + * + *
If no PEM data is found, an {@code IllegalArgumentException} is + * thrown. + * + * @param is InputStream containing PEM data + * @return a {@code DEREncodable} + * @throws IOException on IO or PEM syntax error where the + * {@code InputStream} did not complete decoding. + * @throws EOFException at the end of the {@code InputStream} + * @throws IllegalArgumentException on error in decoding + * @throws NullPointerException when {@code is} is null + */ + public DEREncodable decode(InputStream is) throws IOException { + Objects.requireNonNull(is); + PEMRecord pem = Pem.readPEM(is); + return decode(pem); + } + + /** + * Decodes and returns a {@code DEREncodable} of the specified class from + * the given PEM string. {@code tClass} must extend {@link DEREncodable} + * and be an appropriate class for the PEM type. + * + *
This method reads the {@code String} until PEM data is found + * or the end of the {@code String} is reached. If no PEM data is found, + * an {@code IllegalArgumentException} is thrown. + * + *
If the class parameter is {@code PEMRecord.class}, + * a {@linkplain PEMRecord} is returned containing the + * type identifier and Base64 encoding. Any non-PEM data preceding + * the PEM header will be stored in {@code leadingData}. Other + * class parameters will not return preceding non-PEM data. + * + *
Input consumed by this method is read in as
+ * {@link java.nio.charset.StandardCharsets#UTF_8 UTF-8}.
+ *
+ * @param Class type parameter that extends {@code DEREncodable}
+ * @param str the String containing PEM data
+ * @param tClass the returned object class that implements
+ * {@code DEREncodable}
+ * @return a {@code DEREncodable} specified by {@code tClass}
+ * @throws IllegalArgumentException on error in decoding or no PEM data
+ * found
+ * @throws ClassCastException if {@code tClass} is invalid for the PEM type
+ * @throws NullPointerException when any input values are null
+ */
+ public S decode(String str, Class tClass) {
+ Objects.requireNonNull(str);
+ try {
+ return decode(new ByteArrayInputStream(
+ str.getBytes(StandardCharsets.UTF_8)), tClass);
+ } catch (IOException e) {
+ // With all data contained in the String, there are no IO ops.
+ throw new IllegalArgumentException(e);
+ }
+ }
+
+ /**
+ * Decodes and returns the specified class for the given
+ * {@link InputStream}. The class must extend {@link DEREncodable} and be
+ * an appropriate class for the PEM type.
+ *
+ *
This method reads from the {@code InputStream} until the end of + * the PEM footer or the end of the stream. If an I/O error occurs, + * the read position in the stream may become inconsistent. + * It is recommended to perform no further decoding operations + * on the {@code InputStream}. + * + *
If the class parameter is {@code PEMRecord.class}, + * a {@linkplain PEMRecord} is returned containing the + * type identifier and Base64 encoding. Any non-PEM data preceding + * the PEM header will be stored in {@code leadingData}. Other + * class parameters will not return preceding non-PEM data. + * + *
If no PEM data is found, an {@code IllegalArgumentException} is
+ * thrown.
+ *
+ * @param Class type parameter that extends {@code DEREncodable}.
+ * @param is an InputStream containing PEM data
+ * @param tClass the returned object class that implements
+ * {@code DEREncodable}.
+ * @return a {@code DEREncodable} typecast to {@code tClass}
+ * @throws IOException on IO or PEM syntax error where the
+ * {@code InputStream} did not complete decoding.
+ * @throws EOFException at the end of the {@code InputStream}
+ * @throws IllegalArgumentException on error in decoding
+ * @throws ClassCastException if {@code tClass} is invalid for the PEM type
+ * @throws NullPointerException when any input values are null
+ *
+ * @see #decode(InputStream)
+ * @see #decode(String, Class)
+ */
+ public S decode(InputStream is, Class tClass)
+ throws IOException {
+ Objects.requireNonNull(is);
+ Objects.requireNonNull(tClass);
+ PEMRecord pem = Pem.readPEM(is);
+
+ if (tClass.isAssignableFrom(PEMRecord.class)) {
+ return tClass.cast(pem);
+ }
+ DEREncodable so = decode(pem);
+
+ /*
+ * If the object is a KeyPair, check if the tClass is set to class
+ * specific to a private or public key. Because PKCS8v2 can be a
+ * KeyPair, it is possible for someone to assume all their PEM private
+ * keys are only PrivateKey and not KeyPair.
+ */
+ if (so instanceof KeyPair kp) {
+ if ((PrivateKey.class).isAssignableFrom(tClass) ||
+ (PKCS8EncodedKeySpec.class).isAssignableFrom(tClass)) {
+ so = kp.getPrivate();
+ }
+ if ((PublicKey.class).isAssignableFrom(tClass) ||
+ (X509EncodedKeySpec.class).isAssignableFrom(tClass)) {
+ so = kp.getPublic();
+ }
+ }
+
+ /*
+ * KeySpec use getKeySpec after the Key has been generated. Even though
+ * returning a binary encoding after the Base64 decoding is ok when the
+ * user wants PKCS8EncodedKeySpec, generating the key verifies the
+ * binary encoding and allows the KeyFactory to use the provider's
+ * KeySpec()
+ */
+
+ if ((EncodedKeySpec.class).isAssignableFrom(tClass) &&
+ so instanceof Key key) {
+ try {
+ // unchecked suppressed as we know tClass comes from KeySpec
+ // KeyType not relevant here. We just want KeyFactory
+ if ((PKCS8EncodedKeySpec.class).isAssignableFrom(tClass)) {
+ so = getKeyFactory(key.getAlgorithm()).
+ getKeySpec(key, PKCS8EncodedKeySpec.class);
+ } else if ((X509EncodedKeySpec.class).isAssignableFrom(tClass)) {
+ so = getKeyFactory(key.getAlgorithm())
+ .getKeySpec(key, X509EncodedKeySpec.class);
+ } else {
+ throw new IllegalArgumentException("Invalid KeySpec.");
+ }
+ } catch (InvalidKeySpecException e) {
+ throw new IllegalArgumentException("Invalid KeySpec " +
+ "specified (" + tClass.getName() +") for key (" +
+ key.getClass().getName() +")", e);
+ }
+ }
+
+ return tClass.cast(so);
+ }
+
+ private KeyFactory getKeyFactory(String algorithm) {
+ if (algorithm == null || algorithm.isEmpty()) {
+ throw new IllegalArgumentException("No algorithm found in " +
+ "the encoding");
+ }
+ try {
+ if (factory == null) {
+ return KeyFactory.getInstance(algorithm);
+ }
+ return KeyFactory.getInstance(algorithm, factory);
+ } catch (GeneralSecurityException e) {
+ throw new IllegalArgumentException(e);
+ }
+ }
+
+ // Convenience method to avoid provider getInstance checks clutter
+ private CertificateFactory getCertFactory(String algorithm) {
+ try {
+ if (factory == null) {
+ return CertificateFactory.getInstance(algorithm);
+ }
+ return CertificateFactory.getInstance(algorithm, factory);
+ } catch (GeneralSecurityException e) {
+ throw new IllegalArgumentException(e);
+ }
+ }
+
+ /**
+ * Returns a copy of this {@code PEMDecoder} instance that uses
+ * {@link KeyFactory} and {@link CertificateFactory} implementations
+ * from the specified {@link Provider} to produce cryptographic objects.
+ * Any errors using the {@code Provider} will occur during decoding.
+ *
+ *
If {@code provider} is {@code null}, a new instance is returned with + * the default provider configuration. + * + * @param provider the factory provider + * @return a new PEMEncoder instance configured to the {@code Provider}. + * @throws NullPointerException if {@code provider} is null + */ + public PEMDecoder withFactory(Provider provider) { + Objects.requireNonNull(provider); + return new PEMDecoder(provider, password); + } + + /** + * Returns a copy of this {@code PEMDecoder} that decodes and decrypts + * encrypted private keys using the specified password. + * Non-encrypted PEM can still be decoded from this instance. + * + * @param password the password to decrypt encrypted PEM data. This array + * is cloned and stored in the new instance. + * @return a new PEMEncoder instance configured for decryption + * @throws NullPointerException if {@code password} is null + */ + public PEMDecoder withDecryption(char[] password) { + Objects.requireNonNull(password); + return new PEMDecoder(factory, new PBEKeySpec(password)); + } +} diff --git a/src/java.base/share/classes/java/security/PEMEncoder.java b/src/java.base/share/classes/java/security/PEMEncoder.java new file mode 100644 index 0000000000000..d54650833305c --- /dev/null +++ b/src/java.base/share/classes/java/security/PEMEncoder.java @@ -0,0 +1,382 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.security; + +import jdk.internal.javac.PreviewFeature; +import sun.security.pkcs.PKCS8Key; +import sun.security.util.DerOutputStream; +import sun.security.util.DerValue; +import sun.security.util.Pem; +import sun.security.x509.AlgorithmId; + +import javax.crypto.*; +import javax.crypto.spec.PBEKeySpec; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.security.cert.*; +import java.security.spec.AlgorithmParameterSpec; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; +import java.util.Objects; +import java.util.concurrent.locks.ReentrantLock; + +/** + * {@code PEMEncoder} implements an encoder for Privacy-Enhanced Mail (PEM) + * data. PEM is a textual encoding used to store and transfer security + * objects, such as asymmetric keys, certificates, and certificate revocation + * lists (CRL). It is defined in RFC 1421 and RFC 7468. PEM consists of a + * Base64-formatted binary encoding enclosed by a type-identifying header + * and footer. + * + *
Encoding may be performed on Java API cryptographic objects that + * implement {@link DEREncodable}. The {@link #encode(DEREncodable)} + * and {@link #encodeToString(DEREncodable)} methods encode a DEREncodable + * into PEM and return the data in a byte array or String. + * + *
Private keys can be encrypted and encoded by configuring a + * {@code PEMEncoder} with the {@linkplain #withEncryption(char[])} method, + * which takes a password and returns a new {@code PEMEncoder} instance + * configured to encrypt the key with that password. Alternatively, a + * private key encrypted as an {@code EncryptedKeyInfo} object can be encoded + * directly to PEM by passing it to the {@code encode} or + * {@code encodeToString} methods. + * + *
PKCS #8 2.0 defines the ASN.1 OneAsymmetricKey structure, which may + * contain both private and public keys. + * {@link KeyPair} objects passed to the {@code encode} or + * {@code encodeToString} methods are encoded as a + * OneAsymmetricKey structure using the "PRIVATE KEY" type. + * + *
When encoding a {@link PEMRecord}, the API surrounds the + * {@linkplain PEMRecord#pem()} with the PEM header and footer + * from {@linkplain PEMRecord#type()}. {@linkplain PEMRecord#leadingData()} is + * not included in the encoding. {@code PEMRecord} will not perform + * validity checks on the data. + * + *
The following lists the supported {@code DEREncodable} classes and + * the PEM types that each are encoded as: + * + *
This class is immutable and thread-safe. + * + *
Here is an example of encoding a {@code PrivateKey} object: + * {@snippet lang = java: + * PEMEncoder pe = PEMEncoder.of(); + * byte[] pemData = pe.encode(privKey); + * } + * + *
Here is an example that encrypts and encodes a private key using the + * specified password: + * {@snippet lang = java: + * PEMEncoder pe = PEMEncoder.of().withEncryption(password); + * byte[] pemData = pe.encode(privKey); + * } + * + * @implNote An implementation may support other PEM types and DEREncodables. + * + * + * @see PEMDecoder + * @see PEMRecord + * @see EncryptedPrivateKeyInfo + * + * @spec https://www.rfc-editor.org/info/rfc1421 + * RFC 1421: Privacy Enhancement for Internet Electronic Mail + * @spec https://www.rfc-editor.org/info/rfc7468 + * RFC 7468: Textual Encodings of PKIX, PKCS, and CMS Structures + * + * @since 25 + */ +@PreviewFeature(feature = PreviewFeature.Feature.PEM_API) +public final class PEMEncoder { + + // Singleton instance of PEMEncoder + private static final PEMEncoder PEM_ENCODER = new PEMEncoder(null); + + // Stores the password for an encrypted encoder that isn't setup yet. + private PBEKeySpec keySpec; + // Stores the key after the encoder is ready to encrypt. The prevents + // repeated SecretKeyFactory calls if the encoder is used on multiple keys. + private SecretKey key; + // Makes SecretKeyFactory generation thread-safe. + private final ReentrantLock lock; + + /** + * Instantiate a {@code PEMEncoder} for Encrypted Private Keys. + * + * @param pbe contains the password spec used for encryption. + */ + private PEMEncoder(PBEKeySpec pbe) { + keySpec = pbe; + key = null; + lock = new ReentrantLock(); + } + + /** + * Returns an instance of {@code PEMEncoder}. + * + * @return a {@code PEMEncoder} + */ + public static PEMEncoder of() { + return PEM_ENCODER; + } + + /** + * Encodes the specified {@code DEREncodable} and returns a PEM encoded + * string. + * + * @param de the {@code DEREncodable} to be encoded + * @return a {@code String} containing the PEM encoded data + * @throws IllegalArgumentException if the {@code DEREncodable} cannot be + * encoded + * @throws NullPointerException if {@code de} is {@code null} + * @see #withEncryption(char[]) + */ + public String encodeToString(DEREncodable de) { + Objects.requireNonNull(de); + return switch (de) { + case PublicKey pu -> buildKey(null, pu.getEncoded()); + case PrivateKey pr -> buildKey(pr.getEncoded(), null); + case KeyPair kp -> { + if (kp.getPublic() == null) { + throw new IllegalArgumentException("KeyPair does not " + + "contain PublicKey."); + } + if (kp.getPrivate() == null) { + throw new IllegalArgumentException("KeyPair does not " + + "contain PrivateKey."); + } + yield buildKey(kp.getPrivate().getEncoded(), + kp.getPublic().getEncoded()); + } + case X509EncodedKeySpec x -> + buildKey(null, x.getEncoded()); + case PKCS8EncodedKeySpec p -> + buildKey(p.getEncoded(), null); + case EncryptedPrivateKeyInfo epki -> { + try { + yield Pem.pemEncoded(Pem.ENCRYPTED_PRIVATE_KEY, + epki.getEncoded()); + } catch (IOException e) { + throw new IllegalArgumentException(e); + } + } + case X509Certificate c -> { + try { + if (isEncrypted()) { + throw new IllegalArgumentException("Certificates " + + "cannot be encrypted"); + } + yield Pem.pemEncoded(Pem.CERTIFICATE, c.getEncoded()); + } catch (CertificateEncodingException e) { + throw new IllegalArgumentException(e); + } + } + case X509CRL crl -> { + try { + if (isEncrypted()) { + throw new IllegalArgumentException("CRLs cannot be " + + "encrypted"); + } + yield Pem.pemEncoded(Pem.X509_CRL, crl.getEncoded()); + } catch (CRLException e) { + throw new IllegalArgumentException(e); + } + } + case PEMRecord rec -> { + if (isEncrypted()) { + throw new IllegalArgumentException("PEMRecord cannot be " + + "encrypted"); + } + yield Pem.pemEncoded(rec); + } + + default -> throw new IllegalArgumentException("PEM does not " + + "support " + de.getClass().getCanonicalName()); + }; + } + + /** + * Encodes the specified {@code DEREncodable} and returns the PEM encoding + * in a byte array. + * + * @param de the {@code DEREncodable} to be encoded + * @return a PEM encoded byte array + * @throws IllegalArgumentException if the {@code DEREncodable} cannot be + * encoded + * @throws NullPointerException if {@code de} is {@code null} + * @see #withEncryption(char[]) + */ + public byte[] encode(DEREncodable de) { + return encodeToString(de).getBytes(StandardCharsets.ISO_8859_1); + } + + /** + * Returns a new {@code PEMEncoder} instance configured for encryption + * with the default algorithm and a given password. + * + *
Only {@link PrivateKey} objects can be encrypted with this newly + * configured instance. Encoding other {@link DEREncodable} objects will + * throw an {@code IllegalArgumentException}. + * + * @implNote + * The default password-based encryption algorithm is defined + * by the {@code jdk.epkcs8.defaultAlgorithm} security property and + * uses the default encryption parameters of the provider that is selected. + * For greater flexibility with encryption options and parameters, use + * {@link EncryptedPrivateKeyInfo#encryptKey(PrivateKey, Key, + * String, AlgorithmParameterSpec, Provider, SecureRandom)} and use the + * returned object with {@link #encode(DEREncodable)}. + * + * @param password the encryption password. The array is cloned and + * stored in the new instance. + * @return a new {@code PEMEncoder} instance configured for encryption + * @throws NullPointerException when password is {@code null} + */ + public PEMEncoder withEncryption(char[] password) { + // PBEKeySpec clones the password + Objects.requireNonNull(password, "password cannot be null."); + return new PEMEncoder(new PBEKeySpec(password)); + } + + /** + * Build PEM encoding. + */ + private String buildKey(byte[] privateBytes, byte[] publicBytes) { + DerOutputStream out = new DerOutputStream(); + Cipher cipher; + + if (privateBytes == null && publicBytes == null) { + throw new IllegalArgumentException("No encoded data given by the " + + "DEREncodable."); + } + + // If `keySpec` is non-null, then `key` hasn't been established. + // Setting a `key' prevents repeated key generations operations. + // withEncryption() is a configuration method and cannot throw an + // exception; therefore generation is delayed. + if (keySpec != null) { + // For thread safety + lock.lock(); + if (key == null) { + try { + key = SecretKeyFactory.getInstance(Pem.DEFAULT_ALGO). + generateSecret(keySpec); + keySpec.clearPassword(); + keySpec = null; + } catch (GeneralSecurityException e) { + throw new IllegalArgumentException("Security property " + + "\"jdk.epkcs8.defaultAlgorithm\" may not specify a " + + "valid algorithm. Operation cannot be performed.", e); + } finally { + lock.unlock(); + } + } else { + lock.unlock(); + } + } + + // If `key` is non-null, this is an encoder ready to encrypt. + if (key != null) { + if (privateBytes == null || publicBytes != null) { + throw new IllegalArgumentException("Can only encrypt a " + + "PrivateKey."); + } + + try { + cipher = Cipher.getInstance(Pem.DEFAULT_ALGO); + cipher.init(Cipher.ENCRYPT_MODE, key); + } catch (GeneralSecurityException e) { + throw new IllegalArgumentException("Security property " + + "\"jdk.epkcs8.defaultAlgorithm\" may not specify a " + + "valid algorithm. Operation cannot be performed.", e); + } + + try { + new AlgorithmId(Pem.getPBEID(Pem.DEFAULT_ALGO), + cipher.getParameters()).encode(out); + out.putOctetString(cipher.doFinal(privateBytes)); + return Pem.pemEncoded(Pem.ENCRYPTED_PRIVATE_KEY, + DerValue.wrap(DerValue.tag_Sequence, out).toByteArray()); + } catch (GeneralSecurityException e) { + throw new IllegalArgumentException(e); + } + } + + // X509 only + if (publicBytes != null && privateBytes == null) { + if (publicBytes.length == 0) { + throw new IllegalArgumentException("No public key encoding " + + "given by the DEREncodable."); + } + + return Pem.pemEncoded(Pem.PUBLIC_KEY, publicBytes); + } + + // PKCS8 only + if (publicBytes == null && privateBytes != null) { + if (privateBytes.length == 0) { + throw new IllegalArgumentException("No private key encoding " + + "given by the DEREncodable."); + } + + return Pem.pemEncoded(Pem.PRIVATE_KEY, privateBytes); + } + + // OneAsymmetricKey + if (privateBytes.length == 0) { + throw new IllegalArgumentException("No private key encoding " + + "given by the DEREncodable."); + } + + if (publicBytes.length == 0) { + throw new IllegalArgumentException("No public key encoding " + + "given by the DEREncodable."); + } + try { + return Pem.pemEncoded(Pem.PRIVATE_KEY, + PKCS8Key.getEncoded(publicBytes, privateBytes)); + } catch (IOException e) { + throw new IllegalArgumentException(e); + } + } + + private boolean isEncrypted() { + return (key != null || keySpec != null); + } +} diff --git a/src/java.base/share/classes/java/security/PEMRecord.java b/src/java.base/share/classes/java/security/PEMRecord.java new file mode 100644 index 0000000000000..dfe951a09637f --- /dev/null +++ b/src/java.base/share/classes/java/security/PEMRecord.java @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.security; + +import jdk.internal.javac.PreviewFeature; + +import sun.security.util.Pem; + +import java.util.Base64; +import java.util.Objects; + +/** + * {@code PEMRecord} is a {@link DEREncodable} that represents Privacy-Enhanced + * Mail (PEM) data by its type and Base64 form. {@link PEMDecoder} and + * {@link PEMEncoder} use {@code PEMRecord} when representing the data as a + * cryptographic object is not desired or the type has no + * {@code DEREncodable}. + * + *
{@code type} and {@code pem} may not be {@code null}. + * {@code leadingData} may be null if no non-PEM data preceded PEM header + * during decoding. {@code leadingData} may be useful for reading metadata + * that accompanies PEM data. + * + *
No validation is performed during instantiation to ensure that + * {@code type} conforms to {@code RFC 7468}, that {@code pem} is valid Base64, + * or that {@code pem} matches the {@code type}. {@code leadingData} is not + * defensively copied and does not return a clone when + * {@linkplain #leadingData()} is called. + * + * @param type the type identifier in the PEM header without PEM syntax labels. + * For a public key, {@code type} would be "PUBLIC KEY". + * @param pem any data between the PEM header and footer. + * @param leadingData any non-PEM data preceding the PEM header when decoding. + * + * @spec https://www.rfc-editor.org/info/rfc7468 + * RFC 7468: Textual Encodings of PKIX, PKCS, and CMS Structures + * + * @see PEMDecoder + * @see PEMEncoder + * + * @since 25 + */ +@PreviewFeature(feature = PreviewFeature.Feature.PEM_API) +public record PEMRecord(String type, String pem, byte[] leadingData) + implements DEREncodable { + + /** + * Creates a {@code PEMRecord} instance with the given parameters. + * + * @param type the type identifier + * @param pem the Base64-encoded data encapsulated by the PEM header and + * footer. + * @param leadingData any non-PEM data read during the decoding process + * before the PEM header. This value maybe {@code null}. + * @throws IllegalArgumentException if the {@code type} is incorrectly + * formatted. + * @throws NullPointerException if {@code type} and/or {@code pem} are + * {@code null}. + */ + public PEMRecord(String type, String pem, byte[] leadingData) { + Objects.requireNonNull(type, "\"type\" cannot be null."); + Objects.requireNonNull(pem, "\"pem\" cannot be null."); + + // With no validity checking on `type`, the constructor accept anything + // including lowercase. The onus is on the caller. + if (type.startsWith("-") || type.startsWith("BEGIN ") || + type.startsWith("END ")) { + throw new IllegalArgumentException("PEM syntax labels found. " + + "Only the PEM type identifier is allowed"); + } + + this.type = type; + this.pem = pem; + this.leadingData = leadingData; + } + + /** + * Creates a {@code PEMRecord} instance with a given {@code type} and + * {@code pem} data in String form. {@code leadingData} is set to null. + * + * @param type the PEM type identifier + * @param pem the Base64-encoded data encapsulated by the PEM header and + * footer. + * @throws IllegalArgumentException if the {@code type} is incorrectly + * formatted. + * @throws NullPointerException if {@code type} and/or {@code pem} are + * {@code null}. + */ + public PEMRecord(String type, String pem) { + this(type, pem, null); + } + + /** + * Returns the binary encoding from the Base64 data contained in + * {@code pem}. + * + * @throws IllegalArgumentException if {@code pem} cannot be decoded. + * @return a new array of the binary encoding each time this + * method is called. + */ + public byte[] getEncoded() { + return Base64.getMimeDecoder().decode(pem); + } + + /** + * Returns the type and Base64 encoding in PEM format. {@code leadingData} + * is not returned by this method. + */ + @Override + public String toString() { + return Pem.pemEncoded(this); + } +} diff --git a/src/java.base/share/classes/java/security/cert/X509CRL.java b/src/java.base/share/classes/java/security/cert/X509CRL.java index 111de1daf0e43..d19618f81ed41 100644 --- a/src/java.base/share/classes/java/security/cert/X509CRL.java +++ b/src/java.base/share/classes/java/security/cert/X509CRL.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -107,7 +107,7 @@ * @see X509Extension */ -public abstract class X509CRL extends CRL implements X509Extension { +public abstract non-sealed class X509CRL extends CRL implements X509Extension, DEREncodable { private transient X500Principal issuerPrincipal; diff --git a/src/java.base/share/classes/java/security/cert/X509Certificate.java b/src/java.base/share/classes/java/security/cert/X509Certificate.java index 4e579a75b1c0d..fe4a472dead1a 100644 --- a/src/java.base/share/classes/java/security/cert/X509Certificate.java +++ b/src/java.base/share/classes/java/security/cert/X509Certificate.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -107,8 +107,8 @@ * @see X509Extension */ -public abstract class X509Certificate extends Certificate -implements X509Extension { +public abstract non-sealed class X509Certificate extends Certificate + implements X509Extension, DEREncodable { @java.io.Serial private static final long serialVersionUID = -2491127588187038216L; diff --git a/src/java.base/share/classes/java/security/spec/PKCS8EncodedKeySpec.java b/src/java.base/share/classes/java/security/spec/PKCS8EncodedKeySpec.java index af7f135d7c43f..917b15e06b660 100644 --- a/src/java.base/share/classes/java/security/spec/PKCS8EncodedKeySpec.java +++ b/src/java.base/share/classes/java/security/spec/PKCS8EncodedKeySpec.java @@ -25,25 +25,35 @@ package java.security.spec; +import java.security.DEREncodable; + /** * This class represents the ASN.1 encoding of a private key, - * encoded according to the ASN.1 type {@code PrivateKeyInfo}. - * The {@code PrivateKeyInfo} syntax is defined in the PKCS#8 standard + * encoded according to the ASN.1 type {@code OneAsymmetricKey}. + * The {@code OneAsymmetricKey} syntax is defined in the PKCS#8 standard * as follows: * *
- * PrivateKeyInfo ::= SEQUENCE {
+ * OneAsymmetricKey ::= SEQUENCE {
* version Version,
* privateKeyAlgorithm PrivateKeyAlgorithmIdentifier,
* privateKey PrivateKey,
- * attributes [0] IMPLICIT Attributes OPTIONAL }
+ * attributes [0] Attributes OPTIONAL,
+ * ...,
+ * [[2: publicKey [1] PublicKey OPTIONAL ]],
+ * ...
+ * }
+ *
+ * PrivateKeyInfo ::= OneAsymmetricKey
*
- * Version ::= INTEGER
+ * Version ::= INTEGER { v1(0), v2(1) }
*
* PrivateKeyAlgorithmIdentifier ::= AlgorithmIdentifier
*
* PrivateKey ::= OCTET STRING
*
+ * PublicKey ::= BIT STRING
+ *
* Attributes ::= SET OF Attribute
*
*
@@ -56,11 +66,14 @@
* @see EncodedKeySpec
* @see X509EncodedKeySpec
*
+ * @spec https://www.rfc-editor.org/info/rfc5958
+ * RFC 5958: Asymmetric Key Packages
+ *
* @since 1.2
*/
-public class PKCS8EncodedKeySpec extends EncodedKeySpec {
-
+public non-sealed class PKCS8EncodedKeySpec extends EncodedKeySpec implements
+ DEREncodable {
/**
* Creates a new {@code PKCS8EncodedKeySpec} with the given encoded key.
*
diff --git a/src/java.base/share/classes/java/security/spec/X509EncodedKeySpec.java b/src/java.base/share/classes/java/security/spec/X509EncodedKeySpec.java
index a405104cd074e..e8b6e5996769f 100644
--- a/src/java.base/share/classes/java/security/spec/X509EncodedKeySpec.java
+++ b/src/java.base/share/classes/java/security/spec/X509EncodedKeySpec.java
@@ -25,6 +25,8 @@
package java.security.spec;
+import java.security.DEREncodable;
+
/**
* This class represents the ASN.1 encoding of a public key,
* encoded according to the ASN.1 type {@code SubjectPublicKeyInfo}.
@@ -49,8 +51,8 @@
* @since 1.2
*/
-public class X509EncodedKeySpec extends EncodedKeySpec {
-
+public non-sealed class X509EncodedKeySpec extends EncodedKeySpec implements
+ DEREncodable {
/**
* Creates a new {@code X509EncodedKeySpec} with the given encoded key.
*
diff --git a/src/java.base/share/classes/javax/crypto/EncryptedPrivateKeyInfo.java b/src/java.base/share/classes/javax/crypto/EncryptedPrivateKeyInfo.java
index 1e4e769a83ee0..90316d7437e04 100644
--- a/src/java.base/share/classes/javax/crypto/EncryptedPrivateKeyInfo.java
+++ b/src/java.base/share/classes/javax/crypto/EncryptedPrivateKeyInfo.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2001, 2024, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2001, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -25,13 +25,18 @@
package javax.crypto;
-import java.io.*;
+import jdk.internal.javac.PreviewFeature;
+
+import sun.security.jca.JCAUtil;
+import sun.security.pkcs.PKCS8Key;
+import sun.security.util.*;
+import sun.security.x509.AlgorithmId;
+
+import javax.crypto.spec.PBEKeySpec;
+import java.io.IOException;
import java.security.*;
import java.security.spec.*;
-import sun.security.x509.AlgorithmId;
-import sun.security.util.DerValue;
-import sun.security.util.DerInputStream;
-import sun.security.util.DerOutputStream;
+import java.util.Objects;
/**
* This class implements the {@code EncryptedPrivateKeyInfo} type
@@ -55,14 +60,14 @@
* @since 1.4
*/
-public class EncryptedPrivateKeyInfo {
+public non-sealed class EncryptedPrivateKeyInfo implements DEREncodable {
// The "encryptionAlgorithm" is stored in either the algid or
// the params field. Precisely, if this object is created by
// {@link #EncryptedPrivateKeyInfo(AlgorithmParameters, byte[])}
// with an uninitialized AlgorithmParameters, the AlgorithmParameters
// object is stored in the params field and algid is set to null.
- // In all other cases, algid is non null and params is null.
+ // In all other cases, algid is non-null and params is null.
private final AlgorithmId algid;
private final AlgorithmParameters params;
@@ -73,19 +78,15 @@ public class EncryptedPrivateKeyInfo {
private final byte[] encoded;
/**
- * Constructs (i.e., parses) an {@code EncryptedPrivateKeyInfo} from
- * its ASN.1 encoding.
+ * Constructs an {@code EncryptedPrivateKeyInfo} from a given encrypted
+ * PKCS#8 ASN.1 encoding.
* @param encoded the ASN.1 encoding of this object. The contents of
* the array are copied to protect against subsequent modification.
- * @exception NullPointerException if the {@code encoded} is
- * {@code null}.
- * @exception IOException if error occurs when parsing the ASN.1 encoding.
+ * @throws NullPointerException if {@code encoded} is {@code null}.
+ * @throws IOException if error occurs when parsing the ASN.1 encoding.
*/
public EncryptedPrivateKeyInfo(byte[] encoded) throws IOException {
- if (encoded == null) {
- throw new NullPointerException("the encoded parameter " +
- "must be non-null");
- }
+ Objects.requireNonNull(encoded);
this.encoded = encoded.clone();
DerValue val = DerValue.wrap(this.encoded);
@@ -201,7 +202,7 @@ public EncryptedPrivateKeyInfo(AlgorithmParameters algParams,
tmp = null;
}
- // one and only one is non null
+ // one and only one is non-null
this.algid = tmp;
this.params = this.algid != null ? null : algParams;
@@ -219,6 +220,17 @@ public EncryptedPrivateKeyInfo(AlgorithmParameters algParams,
this.encoded = null;
}
+ /**
+ * Create an EncryptedPrivateKeyInfo object from the given components
+ */
+ private EncryptedPrivateKeyInfo(byte[] encoded, byte[] eData,
+ AlgorithmId id, AlgorithmParameters p) {
+ this.encoded = encoded;
+ encryptedData = eData;
+ algid = id;
+ params = p;
+ }
+
/**
* Returns the encryption algorithm.
* Note: Standard name is returned instead of the specified one @@ -308,6 +320,242 @@ private PKCS8EncodedKeySpec getKeySpecImpl(Key decryptKey, } } + /** + * Creates and encrypts an {@code EncryptedPrivateKeyInfo} from a given + * {@code PrivateKey}. A valid password-based encryption (PBE) algorithm + * and password must be specified. + * + *
The PBE algorithm string format details can be found in the
+ *
+ * Cipher section of the Java Security Standard Algorithm Names
+ * Specification.
+ *
+ * @param key the {@code PrivateKey} to be encrypted
+ * @param password the password used in the PBE encryption. This array
+ * will be cloned before being used.
+ * @param algorithm the PBE encryption algorithm. The default algorithm
+ * will be used if {@code null}. However, {@code null} is
+ * not allowed when {@code params} is non-null.
+ * @param params the {@code AlgorithmParameterSpec} to be used with
+ * encryption. The provider default will be used if
+ * {@code null}.
+ * @param provider the {@code Provider} will be used for PBE
+ * {@link SecretKeyFactory} generation and {@link Cipher}
+ * encryption operations. The default provider list will be
+ * used if {@code null}.
+ * @return an {@code EncryptedPrivateKeyInfo}
+ * @throws IllegalArgumentException on initialization errors based on the
+ * arguments passed to the method
+ * @throws RuntimeException on an encryption error
+ * @throws NullPointerException if the key or password are {@code null}. If
+ * {@code params} is non-null when {@code algorithm} is {@code null}.
+ *
+ * @implNote The {@code jdk.epkcs8.defaultAlgorithm} Security Property
+ * defines the default encryption algorithm and the
+ * {@code AlgorithmParameterSpec} are the provider's algorithm defaults.
+ *
+ * @since 25
+ */
+ @PreviewFeature(feature = PreviewFeature.Feature.PEM_API)
+ public static EncryptedPrivateKeyInfo encryptKey(PrivateKey key,
+ char[] password, String algorithm, AlgorithmParameterSpec params,
+ Provider provider) {
+
+ SecretKey skey;
+ Objects.requireNonNull(key, "key cannot be null");
+ Objects.requireNonNull(password, "password cannot be null.");
+ PBEKeySpec keySpec = new PBEKeySpec(password);
+ if (algorithm == null) {
+ if (params != null) {
+ throw new NullPointerException("algorithm must be specified" +
+ " if params is non-null.");
+ }
+ algorithm = Pem.DEFAULT_ALGO;
+ }
+
+ try {
+ SecretKeyFactory factory;
+ if (provider == null) {
+ factory = SecretKeyFactory.getInstance(algorithm);
+ } else {
+ factory = SecretKeyFactory.getInstance(algorithm, provider);
+ }
+ skey = factory.generateSecret(keySpec);
+ } catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
+ throw new IllegalArgumentException(e);
+ }
+ return encryptKeyImpl(key, algorithm, skey, params, provider, null);
+ }
+
+ /**
+ * Creates and encrypts an {@code EncryptedPrivateKeyInfo} from a given
+ * {@code PrivateKey} and password. Default algorithm and parameters are
+ * used.
+ *
+ * @param key the {@code PrivateKey} to be encrypted
+ * @param password the password used in the PBE encryption. This array
+ * will be cloned before being used.
+ * @return an {@code EncryptedPrivateKeyInfo}
+ * @throws IllegalArgumentException on initialization errors based on the
+ * arguments passed to the method
+ * @throws RuntimeException on an encryption error
+ * @throws NullPointerException when the {@code key} or {@code password}
+ * is {@code null}
+ *
+ * @implNote The {@code jdk.epkcs8.defaultAlgorithm} Security Property
+ * defines the default encryption algorithm and the
+ * {@code AlgorithmParameterSpec} are the provider's algorithm defaults.
+ *
+ * @since 25
+ */
+ @PreviewFeature(feature = PreviewFeature.Feature.PEM_API)
+ public static EncryptedPrivateKeyInfo encryptKey(PrivateKey key,
+ char[] password) {
+ return encryptKey(key, password, Pem.DEFAULT_ALGO, null, null);
+ }
+
+ /**
+ * Creates and encrypts an {@code EncryptedPrivateKeyInfo} from the given
+ * {@link PrivateKey} using the {@code encKey} and given parameters.
+ *
+ * @param key the {@code PrivateKey} to be encrypted
+ * @param encKey the password-based encryption (PBE) {@code Key} used to
+ * encrypt {@code key}.
+ * @param algorithm the PBE encryption algorithm. The default algorithm is
+ * will be used if {@code null}; however, {@code null} is
+ * not allowed when {@code params} is non-null.
+ * @param params the {@code AlgorithmParameterSpec} to be used with
+ * encryption. The provider list default will be used if
+ * {@code null}.
+ * @param random the {@code SecureRandom} instance used during
+ * encryption. The default will be used if {@code null}.
+ * @param provider the {@code Provider} is used for {@link Cipher}
+ * encryption operation. The default provider list will be
+ * used if {@code null}.
+ * @return an {@code EncryptedPrivateKeyInfo}
+ * @throws IllegalArgumentException on initialization errors based on the
+ * arguments passed to the method
+ * @throws RuntimeException on an encryption error
+ * @throws NullPointerException if the {@code key} or {@code encKey} are
+ * {@code null}. If {@code params} is non-null, {@code algorithm} cannot be
+ * {@code null}.
+ *
+ * @implNote The {@code jdk.epkcs8.defaultAlgorithm} Security Property
+ * defines the default encryption algorithm and the
+ * {@code AlgorithmParameterSpec} are the provider's algorithm defaults.
+ *
+ * @since 25
+ */
+ @PreviewFeature(feature = PreviewFeature.Feature.PEM_API)
+ public static EncryptedPrivateKeyInfo encryptKey(PrivateKey key, Key encKey,
+ String algorithm, AlgorithmParameterSpec params, Provider provider,
+ SecureRandom random) {
+
+ Objects.requireNonNull(key);
+ Objects.requireNonNull(encKey);
+ if (algorithm == null) {
+ if (params != null) {
+ throw new NullPointerException("algorithm must be specified " +
+ "if params is non-null.");
+ }
+ algorithm = Pem.DEFAULT_ALGO;
+ }
+ return encryptKeyImpl(key, algorithm, encKey, params, provider, random);
+ }
+
+ private static EncryptedPrivateKeyInfo encryptKeyImpl(PrivateKey key,
+ String algorithm, Key encryptKey, AlgorithmParameterSpec params,
+ Provider provider, SecureRandom random) {
+ AlgorithmId algId;
+ byte[] encryptedData;
+ Cipher c;
+ DerOutputStream out;
+
+ if (random == null) {
+ random = JCAUtil.getDefSecureRandom();
+ }
+ try {
+ if (provider == null) {
+ c = Cipher.getInstance(algorithm);
+ } else {
+ c = Cipher.getInstance(algorithm, provider);
+ }
+ c.init(Cipher.ENCRYPT_MODE, encryptKey, params, random);
+ encryptedData = c.doFinal(key.getEncoded());
+ algId = new AlgorithmId(Pem.getPBEID(algorithm), c.getParameters());
+ out = new DerOutputStream();
+ algId.encode(out);
+ out.putOctetString(encryptedData);
+ } catch (InvalidAlgorithmParameterException | NoSuchAlgorithmException |
+ NoSuchPaddingException e) {
+ throw new IllegalArgumentException(e);
+ } catch (IllegalBlockSizeException | BadPaddingException |
+ InvalidKeyException e) {
+ throw new RuntimeException(e);
+ }
+ return new EncryptedPrivateKeyInfo(
+ DerValue.wrap(DerValue.tag_Sequence, out).toByteArray(),
+ encryptedData, algId, c.getParameters());
+ }
+
+ /**
+ * Extract the enclosed {@code PrivateKey} object from the encrypted data
+ * and return it.
+ *
+ * @param password the password used in the PBE encryption. This array
+ * will be cloned before being used.
+ * @return a {@code PrivateKey}
+ * @throws GeneralSecurityException if an error occurs parsing or
+ * decrypting the encrypted data, or producing the key object.
+ * @throws NullPointerException if {@code password} is null
+ *
+ * @since 25
+ */
+ @PreviewFeature(feature = PreviewFeature.Feature.PEM_API)
+ public PrivateKey getKey(char[] password) throws GeneralSecurityException {
+ SecretKeyFactory skf;
+ PKCS8EncodedKeySpec p8KeySpec;
+ Objects.requireNonNull(password, "password cannot be null");
+ PBEKeySpec keySpec = new PBEKeySpec(password);
+ skf = SecretKeyFactory.getInstance(getAlgName());
+ p8KeySpec = getKeySpec(skf.generateSecret(keySpec));
+
+ return PKCS8Key.parseKey(p8KeySpec.getEncoded());
+ }
+
+ /**
+ * Extract the enclosed {@code PrivateKey} object from the encrypted data
+ * and return it.
+ *
+ * @param decryptKey the decryption key and cannot be {@code null}
+ * @param provider the {@code Provider} used for Cipher decryption and
+ * {@code PrivateKey} generation. A {@code null} value will
+ * use the default provider configuration.
+ * @return a {@code PrivateKey}
+ * @throws GeneralSecurityException if an error occurs parsing or
+ * decrypting the encrypted data, or producing the key object.
+ * @throws NullPointerException if {@code decryptKey} is null
+ *
+ * @since 25
+ */
+ @PreviewFeature(feature = PreviewFeature.Feature.PEM_API)
+ public PrivateKey getKey(Key decryptKey, Provider provider)
+ throws GeneralSecurityException {
+ Objects.requireNonNull(decryptKey,"decryptKey cannot be null.");
+ PKCS8EncodedKeySpec p = getKeySpecImpl(decryptKey, provider);
+ try {
+ if (provider == null) {
+ return KeyFactory.getInstance(
+ KeyUtil.getAlgorithm(p.getEncoded())).
+ generatePrivate(p);
+ }
+ return KeyFactory.getInstance(KeyUtil.getAlgorithm(p.getEncoded()),
+ provider).generatePrivate(p);
+ } catch (IOException e) {
+ throw new GeneralSecurityException(e);
+ }
+ }
+
/**
* Extract the enclosed PKCS8EncodedKeySpec object from the
* encrypted data and return it.
@@ -353,12 +601,8 @@ public PKCS8EncodedKeySpec getKeySpec(Key decryptKey)
public PKCS8EncodedKeySpec getKeySpec(Key decryptKey,
String providerName) throws NoSuchProviderException,
NoSuchAlgorithmException, InvalidKeyException {
- if (decryptKey == null) {
- throw new NullPointerException("decryptKey is null");
- }
- if (providerName == null) {
- throw new NullPointerException("provider is null");
- }
+ Objects.requireNonNull(decryptKey, "decryptKey is null");
+ Objects.requireNonNull(providerName, "provider is null");
Provider provider = Security.getProvider(providerName);
if (provider == null) {
throw new NoSuchProviderException("provider " +
@@ -387,12 +631,8 @@ public PKCS8EncodedKeySpec getKeySpec(Key decryptKey,
public PKCS8EncodedKeySpec getKeySpec(Key decryptKey,
Provider provider) throws NoSuchAlgorithmException,
InvalidKeyException {
- if (decryptKey == null) {
- throw new NullPointerException("decryptKey is null");
- }
- if (provider == null) {
- throw new NullPointerException("provider is null");
- }
+ Objects.requireNonNull(decryptKey, "decryptKey is null");
+ Objects.requireNonNull(provider, "provider is null");
return getKeySpecImpl(decryptKey, provider);
}
@@ -438,23 +678,9 @@ private static void checkTag(DerValue val, byte tag, String valName)
}
}
- @SuppressWarnings("fallthrough")
private static PKCS8EncodedKeySpec pkcs8EncodingToSpec(byte[] encodedKey)
throws IOException {
- DerInputStream in = new DerInputStream(encodedKey);
- DerValue[] values = in.getSequence(3);
-
- switch (values.length) {
- case 4:
- checkTag(values[3], DerValue.TAG_CONTEXT, "attributes");
- /* fall through */
- case 3:
- checkTag(values[0], DerValue.tag_Integer, "version");
- String keyAlg = AlgorithmId.parse(values[1]).getName();
- checkTag(values[2], DerValue.tag_OctetString, "privateKey");
- return new PKCS8EncodedKeySpec(encodedKey, keyAlg);
- default:
- throw new IOException("invalid key encoding");
- }
+ return new PKCS8EncodedKeySpec(encodedKey,
+ KeyUtil.getAlgorithm(encodedKey));
}
}
diff --git a/src/java.base/share/classes/jdk/internal/javac/PreviewFeature.java b/src/java.base/share/classes/jdk/internal/javac/PreviewFeature.java
index 5726671dfd45d..d4bcd34dbd609 100644
--- a/src/java.base/share/classes/jdk/internal/javac/PreviewFeature.java
+++ b/src/java.base/share/classes/jdk/internal/javac/PreviewFeature.java
@@ -80,6 +80,8 @@ public enum Feature {
KEY_DERIVATION,
@JEP(number = 502, title = "Stable Values", status = "Preview")
STABLE_VALUES,
+ @JEP(number=470, title="PEM Encodings of Cryptographic Objects", status="Preview")
+ PEM_API,
LANGUAGE_MODEL,
/**
* A key for testing.
diff --git a/src/java.base/share/classes/sun/security/ec/ECKeyFactory.java b/src/java.base/share/classes/sun/security/ec/ECKeyFactory.java
index 85d4d0bf2639b..cd84fd4dec3d1 100644
--- a/src/java.base/share/classes/sun/security/ec/ECKeyFactory.java
+++ b/src/java.base/share/classes/sun/security/ec/ECKeyFactory.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2006, 2022, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2006, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -25,6 +25,8 @@
package sun.security.ec;
+import sun.security.pkcs.PKCS8Key;
+
import java.security.*;
import java.security.interfaces.*;
import java.security.spec.*;
@@ -84,8 +86,7 @@ public ECKeyFactory() {
* To be used by future Java ECDSA and ECDH implementations.
*/
public static ECKey toECKey(Key key) throws InvalidKeyException {
- if (key instanceof ECKey) {
- ECKey ecKey = (ECKey)key;
+ if (key instanceof ECKey ecKey) {
checkKey(ecKey);
return ecKey;
} else {
@@ -147,7 +148,7 @@ protected Key engineTranslateKey(Key key) throws InvalidKeyException {
// see JCA doc
protected PublicKey engineGeneratePublic(KeySpec keySpec)
- throws InvalidKeySpecException {
+ throws InvalidKeySpecException {
try {
return implGeneratePublic(keySpec);
} catch (InvalidKeySpecException e) {
@@ -159,7 +160,7 @@ protected PublicKey engineGeneratePublic(KeySpec keySpec)
// see JCA doc
protected PrivateKey engineGeneratePrivate(KeySpec keySpec)
- throws InvalidKeySpecException {
+ throws InvalidKeySpecException {
try {
return implGeneratePrivate(keySpec);
} catch (InvalidKeySpecException e) {
@@ -171,19 +172,13 @@ protected PrivateKey engineGeneratePrivate(KeySpec keySpec)
// internal implementation of translateKey() for public keys. See JCA doc
private PublicKey implTranslatePublicKey(PublicKey key)
- throws InvalidKeyException {
- if (key instanceof ECPublicKey) {
- if (key instanceof ECPublicKeyImpl) {
- return key;
- }
- ECPublicKey ecKey = (ECPublicKey)key;
- return new ECPublicKeyImpl(
- ecKey.getW(),
- ecKey.getParams()
- );
+ throws InvalidKeyException {
+ if (key instanceof ECPublicKeyImpl) {
+ return key;
+ } else if (key instanceof ECPublicKey ecKey) {
+ return new ECPublicKeyImpl(ecKey.getW(), ecKey.getParams());
} else if ("X.509".equals(key.getFormat())) {
- byte[] encoded = key.getEncoded();
- return new ECPublicKeyImpl(encoded);
+ return new ECPublicKeyImpl(key.getEncoded());
} else {
throw new InvalidKeyException("Public keys must be instance "
+ "of ECPublicKey or have X.509 encoding");
@@ -192,16 +187,11 @@ private PublicKey implTranslatePublicKey(PublicKey key)
// internal implementation of translateKey() for private keys. See JCA doc
private PrivateKey implTranslatePrivateKey(PrivateKey key)
- throws InvalidKeyException {
- if (key instanceof ECPrivateKey) {
- if (key instanceof ECPrivateKeyImpl) {
- return key;
- }
- ECPrivateKey ecKey = (ECPrivateKey)key;
- return new ECPrivateKeyImpl(
- ecKey.getS(),
- ecKey.getParams()
- );
+ throws InvalidKeyException {
+ if (key instanceof ECPrivateKeyImpl) {
+ return key;
+ } else if (key instanceof ECPrivateKey ecKey) {
+ return new ECPrivateKeyImpl(ecKey.getS(), ecKey.getParams());
} else if ("PKCS#8".equals(key.getFormat())) {
byte[] encoded = key.getEncoded();
try {
@@ -209,52 +199,54 @@ private PrivateKey implTranslatePrivateKey(PrivateKey key)
} finally {
Arrays.fill(encoded, (byte)0);
}
- } else {
- throw new InvalidKeyException("Private keys must be instance "
- + "of ECPrivateKey or have PKCS#8 encoding");
}
+
+ throw new InvalidKeyException("Private keys must be instance "
+ + "of ECPrivateKey or have PKCS#8 encoding");
}
// internal implementation of generatePublic. See JCA doc
private PublicKey implGeneratePublic(KeySpec keySpec)
- throws GeneralSecurityException {
- if (keySpec instanceof X509EncodedKeySpec) {
- X509EncodedKeySpec x509Spec = (X509EncodedKeySpec)keySpec;
- return new ECPublicKeyImpl(x509Spec.getEncoded());
- } else if (keySpec instanceof ECPublicKeySpec) {
- ECPublicKeySpec ecSpec = (ECPublicKeySpec)keySpec;
- return new ECPublicKeyImpl(
- ecSpec.getW(),
- ecSpec.getParams()
- );
- } else {
- throw new InvalidKeySpecException("Only ECPublicKeySpec "
- + "and X509EncodedKeySpec supported for EC public keys");
- }
+ throws GeneralSecurityException {
+ return switch (keySpec) {
+ case X509EncodedKeySpec x -> new ECPublicKeyImpl(x.getEncoded());
+ case ECPublicKeySpec e ->
+ new ECPublicKeyImpl(e.getW(), e.getParams());
+ case PKCS8EncodedKeySpec p8 -> {
+ PKCS8Key p8key = new ECPrivateKeyImpl(p8.getEncoded());
+ if (!p8key.hasPublicKey()) {
+ throw new InvalidKeySpecException("No public key found.");
+ }
+ yield new ECPublicKeyImpl(p8key.getPubKeyEncoded());
+ }
+ default ->
+ throw new InvalidKeySpecException(keySpec.getClass().getName() +
+ " not supported.");
+ };
}
// internal implementation of generatePrivate. See JCA doc
private PrivateKey implGeneratePrivate(KeySpec keySpec)
- throws GeneralSecurityException {
- if (keySpec instanceof PKCS8EncodedKeySpec) {
- PKCS8EncodedKeySpec pkcsSpec = (PKCS8EncodedKeySpec)keySpec;
- byte[] encoded = pkcsSpec.getEncoded();
- try {
- return new ECPrivateKeyImpl(encoded);
- } finally {
- Arrays.fill(encoded, (byte) 0);
+ throws GeneralSecurityException {
+ return switch (keySpec) {
+ case PKCS8EncodedKeySpec p8 -> {
+ byte[] encoded = p8.getEncoded();
+ try {
+ yield new ECPrivateKeyImpl(encoded);
+ } finally {
+ Arrays.fill(encoded, (byte) 0);
+ }
}
- } else if (keySpec instanceof ECPrivateKeySpec) {
- ECPrivateKeySpec ecSpec = (ECPrivateKeySpec)keySpec;
- return new ECPrivateKeyImpl(ecSpec.getS(), ecSpec.getParams());
- } else {
- throw new InvalidKeySpecException("Only ECPrivateKeySpec "
- + "and PKCS8EncodedKeySpec supported for EC private keys");
- }
+ case ECPrivateKeySpec e ->
+ new ECPrivateKeyImpl(e.getS(), e.getParams());
+ default ->
+ throw new InvalidKeySpecException(keySpec.getClass().getName() +
+ " not supported.");
+ };
}
protected
diff --git a/src/java.base/share/classes/sun/security/rsa/RSAPrivateKeyImpl.java b/src/java.base/share/classes/sun/security/rsa/RSAPrivateKeyImpl.java
index 91bc5f771d6bf..c67f934b0c3bc 100644
--- a/src/java.base/share/classes/sun/security/rsa/RSAPrivateKeyImpl.java
+++ b/src/java.base/share/classes/sun/security/rsa/RSAPrivateKeyImpl.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003, 2024, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2003, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -110,7 +110,7 @@ public final class RSAPrivateKeyImpl extends PKCS8Key implements RSAPrivateKey {
out.putInteger(0);
out.putInteger(0);
DerValue val = DerValue.wrap(DerValue.tag_Sequence, out);
- key = val.toByteArray();
+ privKeyMaterial = val.toByteArray();
val.clear();
}
diff --git a/src/java.base/share/classes/sun/security/rsa/RSAPublicKeyImpl.java b/src/java.base/share/classes/sun/security/rsa/RSAPublicKeyImpl.java
index 5a0745604d2d6..47d95cdd6a0d6 100644
--- a/src/java.base/share/classes/sun/security/rsa/RSAPublicKeyImpl.java
+++ b/src/java.base/share/classes/sun/security/rsa/RSAPublicKeyImpl.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003, 2023, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2003, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -82,7 +82,7 @@ public static RSAPublicKey newKey(KeyType type, String format,
break;
case "PKCS#1":
try {
- BigInteger[] comps = parseASN1(encoded);
+ BigInteger[] comps = parsePKCS1(encoded);
key = new RSAPublicKeyImpl(type, null, comps[0], comps[1]);
} catch (IOException ioe) {
throw new InvalidKeyException("Invalid PKCS#1 encoding", ioe);
@@ -199,7 +199,7 @@ public AlgorithmParameterSpec getParams() {
// utility method for parsing DER encoding of RSA public keys in PKCS#1
// format as defined in RFC 8017 Appendix A.1.1, i.e. SEQ of n and e.
- private static BigInteger[] parseASN1(byte[] raw) throws IOException {
+ private static BigInteger[] parsePKCS1(byte[] raw) throws IOException {
DerValue derValue = new DerValue(raw);
if (derValue.tag != DerValue.tag_Sequence) {
throw new IOException("Not a SEQUENCE");
@@ -218,7 +218,7 @@ private static BigInteger[] parseASN1(byte[] raw) throws IOException {
*/
protected void parseKeyBits() throws InvalidKeyException {
try {
- BigInteger[] comps = parseASN1(getKey().toByteArray());
+ BigInteger[] comps = parsePKCS1(getKey().toByteArray());
n = comps[0];
e = comps[1];
} catch (IOException e) {
diff --git a/src/java.base/share/classes/sun/security/util/DerValue.java b/src/java.base/share/classes/sun/security/util/DerValue.java
index f2fcf350b3947..19e7083180b2e 100644
--- a/src/java.base/share/classes/sun/security/util/DerValue.java
+++ b/src/java.base/share/classes/sun/security/util/DerValue.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1996, 2024, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1996, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -67,6 +67,7 @@ public class DerValue {
/** The tag class types */
public static final byte TAG_UNIVERSAL = (byte)0x000;
+ public static final byte TAG_CONSTRUCT = (byte)0x020;
public static final byte TAG_APPLICATION = (byte)0x040;
public static final byte TAG_CONTEXT = (byte)0x080;
public static final byte TAG_PRIVATE = (byte)0x0c0;
diff --git a/src/java.base/share/classes/sun/security/util/KeyUtil.java b/src/java.base/share/classes/sun/security/util/KeyUtil.java
index 03768e114503e..7a58ac0d4e9ab 100644
--- a/src/java.base/share/classes/sun/security/util/KeyUtil.java
+++ b/src/java.base/share/classes/sun/security/util/KeyUtil.java
@@ -41,6 +41,7 @@
import jdk.internal.access.SharedSecrets;
import sun.security.jca.JCAUtil;
+import sun.security.x509.AlgorithmId;
/**
* A utility class to get key length, validate keys, etc.
@@ -478,5 +479,72 @@ public static void destroySecretKeys(SecretKey... keys) {
}
}
}
+
+ /**
+ * With a given DER encoded bytes, read through and return the AlgorithmID
+ * stored if it can be found. If none is found or there is an IOException,
+ * null is returned.
+ *
+ * @param encoded DER encoded bytes
+ * @return AlgorithmID stored in the DER encoded bytes or null.
+ */
+ public static String getAlgorithm(byte[] encoded) throws IOException {
+ try {
+ return getAlgorithmId(encoded).getName();
+ } catch (IOException e) {
+ throw new IOException("No recognized algorithm detected in " +
+ "encoding", e);
+ }
+ }
+
+ /**
+ * With a given DER encoded bytes, read through and return the AlgorithmID
+ * stored if it can be found.
+ *
+ * @param encoded DER encoded bytes
+ * @return AlgorithmID stored in the DER encoded bytes
+ * @throws IOException if there was a DER or other parsing error
+ */
+ public static AlgorithmId getAlgorithmId(byte[] encoded) throws IOException {
+ DerInputStream is = new DerInputStream(encoded);
+ DerValue value = is.getDerValue();
+ if (value.tag != DerValue.tag_Sequence) {
+ throw new IOException("Unknown DER Format: Value 1 not a Sequence");
+ }
+
+ is = value.data;
+ value = is.getDerValue();
+ // This route is for: RSAPublic, Encrypted RSAPrivate, EC Public,
+ // Encrypted EC Private,
+ if (value.tag == DerValue.tag_Sequence) {
+ return AlgorithmId.parse(value);
+ } else if (value.tag == DerValue.tag_Integer) {
+ // RSAPrivate, ECPrivate
+ // current value is version, which can be ignored
+ value = is.getDerValue();
+ if (value.tag == DerValue.tag_OctetString) {
+ value = is.getDerValue();
+ if (value.tag == DerValue.tag_Sequence) {
+ return AlgorithmId.parse(value);
+ } else {
+ // OpenSSL/X9.62 (0xA0)
+ ObjectIdentifier oid = value.data.getOID();
+ AlgorithmId algo = new AlgorithmId(oid, (AlgorithmParameters) null);
+ if (CurveDB.lookup(algo.getName()) != null) {
+ return new AlgorithmId(AlgorithmId.EC_oid);
+ }
+
+ }
+
+ } else if (value.tag == DerValue.tag_Sequence) {
+ // Public Key
+ return AlgorithmId.parse(value);
+ }
+
+ }
+ throw new IOException("No algorithm detected");
+ }
+
+
}
diff --git a/src/java.base/share/classes/sun/security/util/Pem.java b/src/java.base/share/classes/sun/security/util/Pem.java
index ba01d91c82e81..0acbec8281a47 100644
--- a/src/java.base/share/classes/sun/security/util/Pem.java
+++ b/src/java.base/share/classes/sun/security/util/Pem.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2015, 2023, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -25,14 +25,56 @@
package sun.security.util;
-import java.io.IOException;
+import sun.security.x509.AlgorithmId;
+
+import java.io.*;
import java.nio.charset.StandardCharsets;
+import java.security.NoSuchAlgorithmException;
+import java.security.PEMRecord;
+import java.security.Security;
+import java.util.Arrays;
import java.util.Base64;
+import java.util.HexFormat;
+import java.util.Objects;
+import java.util.regex.Pattern;
/**
* A utility class for PEM format encoding.
*/
public class Pem {
+ private static final char WS = 0x20; // Whitespace
+ private static final byte[] CRLF = new byte[] {'\r', '\n'};
+
+ // Default algorithm from jdk.epkcs8.defaultAlgorithm in java.security
+ public static final String DEFAULT_ALGO;
+
+ // Pattern matching for EKPI operations
+ private static final Pattern pbePattern;
+
+ // Lazy initialized PBES2 OID value
+ private static ObjectIdentifier PBES2OID;
+
+ // Lazy initialized singleton encoder.
+ private static Base64.Encoder b64Encoder;
+
+ static {
+ String algo = Security.getProperty("jdk.epkcs8.defaultAlgorithm");
+ DEFAULT_ALGO = (algo == null || algo.isBlank()) ?
+ "PBEWithHmacSHA256AndAES_128" : algo;
+ pbePattern = Pattern.compile("^PBEWith.*And.*",
+ Pattern.CASE_INSENSITIVE);
+ }
+
+ public static final String CERTIFICATE = "CERTIFICATE";
+ public static final String X509_CRL = "X509 CRL";
+ public static final String ENCRYPTED_PRIVATE_KEY = "ENCRYPTED PRIVATE KEY";
+ public static final String PRIVATE_KEY = "PRIVATE KEY";
+ public static final String RSA_PRIVATE_KEY = "RSA PRIVATE KEY";
+ public static final String PUBLIC_KEY = "PUBLIC KEY";
+ // old PEM types per RFC 7468
+ public static final String X509_CERTIFICATE = "X509 CERTIFICATE";
+ public static final String X_509_CERTIFICATE = "X.509 CERTIFICATE";
+ public static final String CRL = "CRL";
/**
* Decodes a PEM-encoded block.
@@ -40,15 +82,264 @@ public class Pem {
* @param input the input string, according to RFC 1421, can only contain
* characters in the base-64 alphabet and whitespaces.
* @return the decoded bytes
- * @throws java.io.IOException if input is invalid
*/
- public static byte[] decode(String input) throws IOException {
- byte[] src = input.replaceAll("\\s+", "")
- .getBytes(StandardCharsets.ISO_8859_1);
- try {
+ public static byte[] decode(String input) {
+ byte[] src = input.replaceAll("\\s+", "").
+ getBytes(StandardCharsets.ISO_8859_1);
return Base64.getDecoder().decode(src);
- } catch (IllegalArgumentException e) {
- throw new IOException(e);
+ }
+
+ /**
+ * Return the OID for a given PBE algorithm. PBES1 has an OID for each
+ * algorithm, while PBES2 has one OID for everything that complies with
+ * the formatting. Therefore, if the algorithm is not PBES1, it will
+ * return PBES2. Cipher will determine if this is a valid PBE algorithm.
+ * PBES2 specifies AES as the cipher algorithm, but any block cipher could
+ * be supported.
+ */
+ public static ObjectIdentifier getPBEID(String algorithm) {
+
+ // Verify pattern matches PBE Standard Name spec
+ if (!pbePattern.matcher(algorithm).matches()) {
+ throw new IllegalArgumentException("Invalid algorithm format.");
+ }
+
+ // Return the PBES1 OID if it matches
+ try {
+ return AlgorithmId.get(algorithm).getOID();
+ } catch (NoSuchAlgorithmException e) {
+ // fall-through
}
+
+ // Lazy initialize
+ if (PBES2OID == null) {
+ try {
+ // Set to the hardcoded OID in KnownOID.java
+ PBES2OID = AlgorithmId.get("PBES2").getOID();
+ } catch (NoSuchAlgorithmException e) {
+ // Should never fail.
+ throw new IllegalArgumentException(e);
+ }
+ }
+ return PBES2OID;
+ }
+
+ /*
+ * RFC 7468 has some rules what generators should return given a historical
+ * type name. This converts read in PEM to the RFC. Change the type to
+ * be uniform is likely to help apps from not using all 3 certificate names.
+ */
+ private static String typeConverter(String type) {
+ return switch (type) {
+ case Pem.X509_CERTIFICATE, Pem.X_509_CERTIFICATE -> Pem.CERTIFICATE;
+ case Pem.CRL -> Pem.X509_CRL;
+ default -> type;
+ };
+ }
+
+ /**
+ * Read the PEM text and return it in it's three components: header,
+ * base64, and footer.
+ *
+ * The method will leave the stream after reading the end of line of the
+ * footer or end of file
+ * @param is an InputStream
+ * @param shortHeader if true, the hyphen length is 4 because the first
+ * hyphen is assumed to have been read. This is needed
+ * for the CertificateFactory X509 implementation.
+ * @return a new PEMRecord
+ * @throws IOException on IO errors or PEM syntax errors that leave
+ * the read position not at the end of a PEM block
+ * @throws EOFException when at the unexpected end of the stream
+ * @throws IllegalArgumentException when a PEM syntax error occurs,
+ * but the read position in the stream is at the end of the block, so
+ * future reads can be successful.
+ */
+ public static PEMRecord readPEM(InputStream is, boolean shortHeader)
+ throws IOException {
+ Objects.requireNonNull(is);
+
+ int hyphen = (shortHeader ? 1 : 0);
+ int eol = 0;
+
+ ByteArrayOutputStream os = new ByteArrayOutputStream(6);
+ // Find starting hyphens
+ do {
+ int d = is.read();
+ switch (d) {
+ case '-' -> hyphen++;
+ case -1 -> {
+ if (os.size() == 0) {
+ throw new EOFException("No data available");
+ }
+ throw new EOFException("No PEM data found");
+ }
+ default -> hyphen = 0;
+ }
+ os.write(d);
+ } while (hyphen != 5);
+
+ StringBuilder sb = new StringBuilder(64);
+ sb.append("-----");
+ hyphen = 0;
+ int c;
+
+ // Get header definition until first hyphen
+ do {
+ switch (c = is.read()) {
+ case '-' -> hyphen++;
+ case -1 -> throw new EOFException("Input ended prematurely");
+ case '\n', '\r' -> throw new IOException("Incomplete header");
+ default -> sb.append((char) c);
+ }
+ } while (hyphen == 0);
+
+ // Verify header ending with 5 hyphens.
+ do {
+ switch (is.read()) {
+ case '-' -> hyphen++;
+ default ->
+ throw new IOException("Incomplete header");
+ }
+ } while (hyphen < 5);
+
+ sb.append("-----");
+ String header = sb.toString();
+ if (header.length() < 16 || !header.startsWith("-----BEGIN ") ||
+ !header.endsWith("-----")) {
+ throw new IOException("Illegal header: " + header);
+ }
+
+ hyphen = 0;
+ sb = new StringBuilder(1024);
+
+ // Determine the line break using the char after the last hyphen
+ switch (is.read()) {
+ case WS -> {} // skip whitespace
+ case '\r' -> {
+ c = is.read();
+ if (c == '\n') {
+ eol = '\n';
+ } else {
+ eol = '\r';
+ sb.append((char) c);
+ }
+ }
+ case '\n' -> eol = '\n';
+ default ->
+ throw new IOException("No EOL character found");
+ }
+
+ // Read data until we find the first footer hyphen.
+ do {
+ switch (c = is.read()) {
+ case -1 ->
+ throw new EOFException("Incomplete header");
+ case '-' -> hyphen++;
+ case WS, '\t', '\r', '\n' -> {} // skip whitespace and tab
+ default -> sb.append((char) c);
+ }
+ } while (hyphen == 0);
+
+ String data = sb.toString();
+
+ // Verify footer starts with 5 hyphens.
+ do {
+ switch (is.read()) {
+ case '-' -> hyphen++;
+ case -1 -> throw new EOFException("Input ended prematurely");
+ default -> throw new IOException("Incomplete footer");
+ }
+ } while (hyphen < 5);
+
+ hyphen = 0;
+ sb = new StringBuilder(64);
+ sb.append("-----");
+
+ // Look for Complete header by looking for the end of the hyphens
+ do {
+ switch (c = is.read()) {
+ case '-' -> hyphen++;
+ case -1 -> throw new EOFException("Input ended prematurely");
+ default -> sb.append((char) c);
+ }
+ } while (hyphen == 0);
+
+ // Verify ending with 5 hyphens.
+ do {
+ switch (is.read()) {
+ case '-' -> hyphen++;
+ case -1 -> throw new EOFException("Input ended prematurely");
+ default -> throw new IOException("Incomplete footer");
+ }
+ } while (hyphen < 5);
+
+ while ((c = is.read()) != eol && c != -1 && c != WS) {
+ // skip when eol is '\n', the line separator is likely "\r\n".
+ if (c == '\r') {
+ continue;
+ }
+ throw new IOException("Invalid PEM format: " +
+ "No EOL char found in footer: 0x" +
+ HexFormat.of().toHexDigits((byte) c));
+ }
+
+ sb.append("-----");
+ String footer = sb.toString();
+ if (footer.length() < 14 || !footer.startsWith("-----END ") ||
+ !footer.endsWith("-----")) {
+ // Not an IOE because the read pointer is correctly at the end.
+ throw new IOException("Illegal footer: " + footer);
+ }
+
+ // Verify the object type in the header and the footer are the same.
+ String headerType = header.substring(11, header.length() - 5);
+ String footerType = footer.substring(9, footer.length() - 5);
+ if (!headerType.equals(footerType)) {
+ throw new IOException("Header and footer do not " +
+ "match: " + headerType + " " + footerType);
+ }
+
+ // If there was data before finding the 5 dashes of the PEM header,
+ // backup 5 characters and save that data.
+ byte[] preData = null;
+ if (os.size() > 5) {
+ preData = Arrays.copyOf(os.toByteArray(), os.size() - 5);
+ }
+
+ return new PEMRecord(typeConverter(headerType), data, preData);
+ }
+
+ public static PEMRecord readPEM(InputStream is) throws IOException {
+ return readPEM(is, false);
+ }
+
+ private static String pemEncoded(String type, String base64) {
+ return
+ "-----BEGIN " + type + "-----\r\n" +
+ base64 + (!base64.endsWith("\n") ? "\r\n" : "") +
+ "-----END " + type + "-----\r\n";
+ }
+
+ /**
+ * Construct a String-based encoding based off the type. leadingData
+ * is not used with this method.
+ * @return PEM in a string
+ */
+ public static String pemEncoded(String type, byte[] der) {
+ if (b64Encoder == null) {
+ b64Encoder = Base64.getMimeEncoder(64, CRLF);
+ }
+ return pemEncoded(type, b64Encoder.encodeToString(der));
+ }
+
+ /**
+ * Construct a String-based encoding based off the type. leadingData
+ * is not used with this method.
+ * @return PEM in a string
+ */
+ public static String pemEncoded(PEMRecord pem) {
+ String p = pem.pem().replaceAll("(.{64})", "$1\r\n");
+ return pemEncoded(pem.type(), p);
}
}
diff --git a/src/java.base/share/classes/sun/security/x509/X509Key.java b/src/java.base/share/classes/sun/security/x509/X509Key.java
index 719748394e195..c83e06f651e80 100644
--- a/src/java.base/share/classes/sun/security/x509/X509Key.java
+++ b/src/java.base/share/classes/sun/security/x509/X509Key.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1996, 2024, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1996, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -36,7 +36,6 @@
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
-import java.util.Objects;
import sun.security.util.HexDumpEncoder;
import sun.security.util.*;
@@ -83,7 +82,8 @@ public X509Key() { }
* data is stored and transmitted losslessly, but no knowledge
* about this particular algorithm is available.
*/
- private X509Key(AlgorithmId algid, BitArray key) {
+ @SuppressWarnings("this-escape")
+ public X509Key(AlgorithmId algid, BitArray key) {
this.algid = algid;
setKey(key);
encode();
@@ -100,7 +100,7 @@ protected void setKey(BitArray key) {
* Gets the key. The key may or may not be byte aligned.
* @return a BitArray containing the key.
*/
- protected BitArray getKey() {
+ public BitArray getKey() {
return (BitArray)bitStringKey.clone();
}
@@ -129,7 +129,7 @@ public static PublicKey parse(DerValue in) throws IOException
algorithm = AlgorithmId.parse(in.data.getDerValue());
try {
subjectKey = buildX509Key(algorithm,
- in.data.getUnalignedBitString());
+ in.data.getUnalignedBitString());
} catch (InvalidKeyException e) {
throw new IOException("subject key, " + e.getMessage(), e);
@@ -154,7 +154,7 @@ public static PublicKey parse(DerValue in) throws IOException
* @exception InvalidKeyException on invalid key encodings.
*/
protected void parseKeyBits() throws InvalidKeyException {
- encode();
+ getEncodedInternal();
}
/*
@@ -243,7 +243,7 @@ public String getAlgorithm() {
/**
* Returns the algorithm ID to be used with this key.
*/
- public AlgorithmId getAlgorithmId() { return algid; }
+ public AlgorithmId getAlgorithmId() { return algid; }
/**
* Encode SubjectPublicKeyInfo sequence on the DER output stream.
@@ -260,7 +260,7 @@ public byte[] getEncoded() {
return getEncodedInternal().clone();
}
- public byte[] getEncodedInternal() {
+ private byte[] getEncodedInternal() {
byte[] encoded = encodedKey;
if (encoded == null) {
DerOutputStream out = new DerOutputStream();
@@ -314,7 +314,7 @@ public String toString()
* @param val a DER-encoded X.509 SubjectPublicKeyInfo value
* @exception InvalidKeyException on parsing errors.
*/
- void decode(DerValue val) throws InvalidKeyException {
+ public void decode(DerValue val) throws InvalidKeyException {
try {
if (val.tag != DerValue.tag_Sequence)
throw new InvalidKeyException("invalid key format");
diff --git a/src/java.base/share/conf/security/java.security b/src/java.base/share/conf/security/java.security
index b115d47983848..b750c1b82b0b0 100644
--- a/src/java.base/share/conf/security/java.security
+++ b/src/java.base/share/conf/security/java.security
@@ -1549,3 +1549,12 @@ jdk.tls.alpnCharset=ISO_8859_1
# security property value defined here.
#
#jdk.security.krb5.name.case.sensitive=false
+
+#
+# Default algorithm for PEMEncoder Encrypted PKCS#8
+#
+# This property defines the default password-based encryption algorithm for
+# java.security.PEMEncoder when configured for encryption with the
+# withEncryption method.
+#
+jdk.epkcs8.defaultAlgorithm=PBEWithHmacSHA256AndAES_128
\ No newline at end of file
diff --git a/test/jdk/java/security/KeyFactory/KeyFactoryGetKeySpecForInvalidSpec.java b/test/jdk/java/security/KeyFactory/KeyFactoryGetKeySpecForInvalidSpec.java
index 71b05b0c2252f..a8346089db7dc 100644
--- a/test/jdk/java/security/KeyFactory/KeyFactoryGetKeySpecForInvalidSpec.java
+++ b/test/jdk/java/security/KeyFactory/KeyFactoryGetKeySpecForInvalidSpec.java
@@ -1,5 +1,6 @@
/*
* Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
diff --git a/test/jdk/java/security/PEM/PEMData.java b/test/jdk/java/security/PEM/PEMData.java
new file mode 100644
index 0000000000000..e1f32cdbb7c82
--- /dev/null
+++ b/test/jdk/java/security/PEM/PEMData.java
@@ -0,0 +1,476 @@
+/*
+ * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import javax.crypto.EncryptedPrivateKeyInfo;
+import java.security.DEREncodable;
+import java.security.KeyPair;
+import java.security.PEMRecord;
+import java.security.cert.X509Certificate;
+import java.security.interfaces.*;
+import java.util.ArrayList;
+import java.util.Base64;
+import java.util.List;
+import java.util.regex.Pattern;
+
+/**
+ * Library class for PEMEncoderTest and PEMDecoderTest
+ */
+class PEMData {
+ public static final Entry ecsecp256 = new Entry("ecsecp256",
+ """
+ -----BEGIN PRIVATE KEY-----
+ MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgkW3Jx561NlEgBnut
+ KwDdi3cNwu7YYD/QtJ+9+AEBdoqhRANCAASL+REY4vvAI9M3gonaml5K3lRgHq5w
+ +OO4oO0VNduC44gUN1nrk7/wdNSpL+xXNEX52Dsff+2RD/fop224ANvB
+ -----END PRIVATE KEY-----
+ """, KeyPair.class);
+
+ public static final Entry rsapriv = new Entry("rsapriv",
+ """
+ -----BEGIN PRIVATE KEY-----
+ MIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBAOtjMnCzPy4jCeZb
+ OdOvmvU3jl7+cvPFgL5MfqDCM5a8yI0yImg/hzibJJHLk3emUVBSnekgHvCqyGLW
+ 3qGR2DuBEaMy0mkg8hfKcSpHLaYjDYaspO27d2qtb6d1qtsPoPjJFjWFYeW6K463
+ OHG654K5/2FcJgQdlLVyp3zCiQU/AgMBAAECgYEAwNkDkTv5rlX8nWLuLJV5kh/T
+ H9a93SRZxw8qy5Bv7bZ7ZNrHP7uUkHbi7iPojKWRhwo43692SdzR0dCSk7LGgN9q
+ CYvndsYR6gifVGBi0WF+St4+NdtcQ3VlNdsojy2BdIx0oC+r7i3bn+zc968O/kI+
+ EgdgrMcjjFqyx6tMHpECQQD8TYPKGHyN7Jdy28llCoUX/sL/yZ2vIi5mnDAFE5ae
+ KZQSkNAXG+8i9Qbs/Wdd5S3oZDqu+6DBn9gib80pYY05AkEA7tY59Oy8ka7nBlGP
+ g6Wo1usF2bKqk8vjko9ioZQay7f86aB10QFcAjCr+cCUm16Lc9DwzWl02nNggRZa
+ Jz8eNwJBAO+1zfLjFOPa14F/JHdlaVKE8EwKCFDuztsapd0M4Vtf8Zk6ERsDpU63
+ Ml9T2zOwnM9g+whpdjDAZ59ATdJ1JrECQQDReJQ2SxeL0lGPCiOLu9RcQp7L81aF
+ 79G1bgp8WlAyEjlAkloiqEWRKiz7DDuKFR7Lwhognng9S+n87aS+PS57AkBh75t8
+ 6onPAs4hkm+63dfzCojvEkALevO8J3OVX7YS5q9J1r75wDn60Ob0Zh+iiorpx8Ob
+ WqcWcoJqfdLEyBT+
+ -----END PRIVATE KEY-----
+ """, RSAPrivateKey.class);
+
+ public static final Entry rsaprivbc = new Entry("rsaprivbc",
+ """
+ -----BEGIN PRIVATE KEY-----
+ MIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBAOtjMnCzPy4jCeZb
+ OdOvmvU3jl7+cvPFgL5MfqDCM5a8yI0yImg/hzibJJHLk3emUVBSnekgHvCqyGLW
+ 3qGR2DuBEaMy0mkg8hfKcSpHLaYjDYaspO27d2qtb6d1qtsPoPjJFjWFYeW6K463
+ OHG654K5/2FcJgQdlLVyp3zCiQU/AgMBAAECgYEAwNkDkTv5rlX8nWLuLJV5kh/T
+ H9a93SRZxw8qy5Bv7bZ7ZNrHP7uUkHbi7iPojKWRhwo43692SdzR0dCSk7LGgN9q
+ CYvndsYR6gifVGBi0WF+St4+NdtcQ3VlNdsojy2BdIx0oC+r7i3bn+zc968O/kI+
+ EgdgrMcjjFqyx6tMHpECQQD8TYPKGHyN7Jdy28llCoUX/sL/yZ2vIi5mnDAFE5ae
+ KZQSkNAXG+8i9Qbs/Wdd5S3oZDqu+6DBn9gib80pYY05AkEA7tY59Oy8ka7nBlGP
+ g6Wo1usF2bKqk8vjko9ioZQay7f86aB10QFcAjCr+cCUm16Lc9DwzWl02nNggRZa
+ Jz8eNwJBAO+1zfLjFOPa14F/JHdlaVKE8EwKCFDuztsapd0M4Vtf8Zk6ERsDpU63
+ Ml9T2zOwnM9g+whpdjDAZ59ATdJ1JrECQQDReJQ2SxeL0lGPCiOLu9RcQp7L81aF
+ 79G1bgp8WlAyEjlAkloiqEWRKiz7DDuKFR7Lwhognng9S+n87aS+PS57AkBh75t8
+ 6onPAs4hkm+63dfzCojvEkALevO8J3OVX7YS5q9J1r75wDn60Ob0Zh+iiorpx8Ob
+ WqcWcoJqfdLEyBT+
+ -----END PRIVATE KEY-----
+ """, RSAPrivateKey.class);
+
+ public static final Entry ec25519priv = new Entry("ed25519priv",
+ """
+ -----BEGIN PRIVATE KEY-----
+ MC4CAQAwBQYDK2VwBCIEIFFZsmD+OKk67Cigc84/2fWtlKsvXWLSoMJ0MHh4jI4I
+ -----END PRIVATE KEY-----
+ """, EdECPrivateKey.class);
+
+ public static final Entry rsapub = new Entry("rsapub",
+ """
+ -----BEGIN PUBLIC KEY-----
+ MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDrYzJwsz8uIwnmWznTr5r1N45e
+ /nLzxYC+TH6gwjOWvMiNMiJoP4c4mySRy5N3plFQUp3pIB7wqshi1t6hkdg7gRGj
+ MtJpIPIXynEqRy2mIw2GrKTtu3dqrW+ndarbD6D4yRY1hWHluiuOtzhxuueCuf9h
+ XCYEHZS1cqd8wokFPwIDAQAB
+ -----END PUBLIC KEY-----
+ """, RSAPublicKey.class);
+
+ public static final Entry rsapubbc = new Entry("rsapubbc",
+ """
+ -----BEGIN PUBLIC KEY-----
+ MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDrYzJwsz8uIwnmWznTr5r1N45e
+ /nLzxYC+TH6gwjOWvMiNMiJoP4c4mySRy5N3plFQUp3pIB7wqshi1t6hkdg7gRGj
+ MtJpIPIXynEqRy2mIw2GrKTtu3dqrW+ndarbD6D4yRY1hWHluiuOtzhxuueCuf9h
+ XCYEHZS1cqd8wokFPwIDAQAB
+ -----END PUBLIC KEY-----
+ """, RSAPublicKey.class);
+
+ public static final Entry ecsecp256pub = new Entry("ecsecp256pub", """
+ -----BEGIN PUBLIC KEY-----
+ MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEi/kRGOL7wCPTN4KJ2ppeSt5UYB6u
+ cPjjuKDtFTXbguOIFDdZ65O/8HTUqS/sVzRF+dg7H3/tkQ/36KdtuADbwQ==
+ -----END PUBLIC KEY-----
+ """, ECPublicKey.class);
+
+ // EC key with explicit parameters -- Not currently supported by SunEC
+ public static final String pubec_explicit = """
+ -----BEGIN PUBLIC KEY-----
+ MIIBSzCCAQMGByqGSM49AgEwgfcCAQEwLAYHKoZIzj0BAQIhAP////8AAAABAAAA
+ AAAAAAAAAAAA////////////////MFsEIP////8AAAABAAAAAAAAAAAAAAAA////
+ ///////////8BCBaxjXYqjqT57PrvVV2mIa8ZR0GsMxTsPY7zjw+J9JgSwMVAMSd
+ NgiG5wSTamZ44ROdJreBn36QBEEEaxfR8uEsQkf4vOblY6RA8ncDfYEt6zOg9KE5
+ RdiYwpZP40Li/hp/m47n60p8D54WK84zV2sxXs7LtkBoN79R9QIhAP////8AAAAA
+ //////////+85vqtpxeehPO5ysL8YyVRAgEBA0IABIv5ERji+8Aj0zeCidqaXkre
+ VGAernD447ig7RU124LjiBQ3WeuTv/B01Kkv7Fc0RfnYOx9/7ZEP9+inbbgA28E=
+ -----END PUBLIC KEY-----
+ """;
+
+ public static final Entry oasbcpem = new Entry("oasbcpem",
+ """
+ -----BEGIN PRIVATE KEY-----
+ MIIDCAIBATANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBAOtjMnCzPy4jCeZbOdOvmvU3jl7+
+ cvPFgL5MfqDCM5a8yI0yImg/hzibJJHLk3emUVBSnekgHvCqyGLW3qGR2DuBEaMy0mkg8hfKcSpH
+ LaYjDYaspO27d2qtb6d1qtsPoPjJFjWFYeW6K463OHG654K5/2FcJgQdlLVyp3zCiQU/AgMBAAEC
+ gYEAwNkDkTv5rlX8nWLuLJV5kh/TH9a93SRZxw8qy5Bv7bZ7ZNrHP7uUkHbi7iPojKWRhwo43692
+ SdzR0dCSk7LGgN9qCYvndsYR6gifVGBi0WF+St4+NdtcQ3VlNdsojy2BdIx0oC+r7i3bn+zc968O
+ /kI+EgdgrMcjjFqyx6tMHpECQQD8TYPKGHyN7Jdy28llCoUX/sL/yZ2vIi5mnDAFE5aeKZQSkNAX
+ G+8i9Qbs/Wdd5S3oZDqu+6DBn9gib80pYY05AkEA7tY59Oy8ka7nBlGPg6Wo1usF2bKqk8vjko9i
+ oZQay7f86aB10QFcAjCr+cCUm16Lc9DwzWl02nNggRZaJz8eNwJBAO+1zfLjFOPa14F/JHdlaVKE
+ 8EwKCFDuztsapd0M4Vtf8Zk6ERsDpU63Ml9T2zOwnM9g+whpdjDAZ59ATdJ1JrECQQDReJQ2SxeL
+ 0lGPCiOLu9RcQp7L81aF79G1bgp8WlAyEjlAkloiqEWRKiz7DDuKFR7Lwhognng9S+n87aS+PS57
+ AkBh75t86onPAs4hkm+63dfzCojvEkALevO8J3OVX7YS5q9J1r75wDn60Ob0Zh+iiorpx8ObWqcW
+ coJqfdLEyBT+gYGNADCBiQKBgQDrYzJwsz8uIwnmWznTr5r1N45e/nLzxYC+TH6gwjOWvMiNMiJo
+ P4c4mySRy5N3plFQUp3pIB7wqshi1t6hkdg7gRGjMtJpIPIXynEqRy2mIw2GrKTtu3dqrW+ndarb
+ D6D4yRY1hWHluiuOtzhxuueCuf9hXCYEHZS1cqd8wokFPwIDAQAB
+ -----END PRIVATE KEY-----
+ """, KeyPair.class);
+
+ public static final Entry oasrfc8410 = new Entry("oasrfc8410",
+ """
+ -----BEGIN PRIVATE KEY-----
+ MHICAQEwBQYDK2VwBCIEINTuctv5E1hK1bbY8fdp+K06/nwoy/HU++CXqI9EdVhC
+ oB8wHQYKKoZIhvcNAQkJFDEPDA1DdXJkbGUgQ2hhaXJzgSEAGb9ECWmEzf6FQbrB
+ Z9w7lshQhqowtrbLDFw4rXAxZuE=
+ -----END PRIVATE KEY-----
+ """, KeyPair.class);
+
+ public static final Entry rsaOpenSSL = new Entry("rsaOpenSSL",
+ """
+ -----BEGIN RSA PRIVATE KEY-----
+ MIIEowIBAAKCAQEAqozTLan1qFcOCWnS63jXQn5lLyGOKDv3GM11n2zkGGrChayj
+ cSzB2KTlDmN9NgOyFdqGNWbSgdmXR5ToHGHYwaKubJoQIoPQcsipWDI156d3+X/8
+ BxCGY8l5nYwvS4olOXc+2kEjeFF1eamnm9IQ5DHZfaFPl0ri4Yfm1YHBAbt/7HvF
+ 3MBjgBj1xSsSFLW4O6ws6guRVGDfKBVyyRNUhRTbSua/nEz0wAjxF2PWT+ZTHS6M
+ 0siYwVTuPI4/n4ItoYoahvGb9JskkXP+bc/QZJCTFYdyxF5tKqVMSdYaJTxop02p
+ Jo3oeafVKSlBrr0K731xgNBKqBud44aKT5R96QIDAQABAoIBAQCD9Q/T7gOvayPm
+ LqXOISJURV1emRTXloX5/8Y5QtQ8/CVjrg6Lm3ikefjsKBgR+cwJUpmyqcrIQyXk
+ cZchlqdSMt/IEW/YdKqMlStJnRfOE+ok9lx2ztdcT9+0AWn6hXmFu/i6f9nE1yoQ
+ py6SxnbhSJyhsnTVd1CR9Uep/InsHvYW/15WlVMD1VuCSIt9sefqXwavbAfBaqbn
+ mjwBB/ulsqKhHSuRq/QWqlj+jyGqhhYmTguC1Qwt0woDbThiHtK+suCTAlGBj/A+
+ IZ1U9d+VsHBcWDKBkxmlKWcJAGR3xXiKKy9vfzC+DU7L99kgay80VZarDyXgiy78
+ 9xMMzRMBAoGBANoxnZhu1bUFtLqTJ1HfDm6UB+1zVd2Mu4DXYdy/AHjoaCLp05OQ
+ 0ZeyhO/eXPT+eGpzCxkWD7465KO/QDfnp54p/NS73jaJVdWQHBhzJx1MymqURy3N
+ JQeW4+ojzwSmVXcrs7Og6EBa4L+PWLpMLW2kODniCY+vp9f5LS6m8UPJAoGBAMgZ
+ 4rBw7B9YFZZW/EE4eos4Q7KtA5tEP6wvCq04oxfiSytWXifYX0ToPp0CHhZlWOxk
+ v9a/BDGqM7AxAQJs7mmIvT5AT2V1w7oTbFPnnAo6pQtLcfaxdFFqr0h6t0sXSOKC
+ rQeZAqqFqwuOyP7vT0goGlBruHkwS21NKkzCyzkhAoGAc2JjhbWu+8Cdt0CUPX5o
+ ol9T5eTlFnkSuuqrTNIQzN+SGkxu341o2QDFvhdoLwLW6OwXhVeeUanROSqtKiMu
+ B70Kf/EtbMephXtk8CUNHTh7nmr1TSo8F8xakHoJQts3PQL2T9qal1W3nnWOpU4d
+ g+qg9TMsfTiV2OdjVlVgJskCgYBSnjV1qjojuue22hVvDFW0c7en5z2M9wHfItEi
+ sjbMnrdwnklj5Dd5qPZpNz2a+59ag0Kd9OJTazXKMoF7MeTCGB4ivMTLXHNCudBJ
+ WGCZ7JrGbhEQzTX8g7L5lwlk7KlANLoiX++03lm//OVKNR6j6ULsH33cM6+A4pJr
+ fSYRYQKBgCr9iMTmL0x+n6AmMNecR+MhDxi99Oy0s2EBAYqN9g/8yNgwM4KR0cjz
+ EcgIOtkvoTrJ9Cquvuj+O7/d2yNoH0SZQ4IYJKq47/Z4kKhwXzJnBCCCBKgkjfub
+ RTQSNnSEgTaBD29l7FrhNRHX9lIKFZ23caCTBS6o3q3+KgPbq7ao
+ -----END RSA PRIVATE KEY-----
+ """, RSAPrivateKey.class);
+
+ static final Entry ed25519ep8 = new Entry("ed25519ep8",
+ """
+ -----BEGIN ENCRYPTED PRIVATE KEY-----
+ MIGqMGYGCSqGSIb3DQEFDTBZMDgGCSqGSIb3DQEFDDArBBRyYnoNyrcqvubzch00
+ jyuAb5YizgICEAACARAwDAYIKoZIhvcNAgkFADAdBglghkgBZQMEAQIEEM8BgEgO
+ vdMyi46+Dw7cOjwEQLtx5ME0NOOo7vlCGm3H/4j+Tf5UXrMb1UrkPjqc8OiLbC0n
+ IycFtI70ciPjgwDSjtCcPxR8fSxJPrm2yOJsRVo=
+ -----END ENCRYPTED PRIVATE KEY-----
+ """, EdECPrivateKey.class, "fish".toCharArray());
+
+ // This is not meant to be decrypted and to stay as an EKPI
+ static final Entry ed25519ekpi = new Entry("ed25519ekpi",
+ ed25519ep8.pem(), EncryptedPrivateKeyInfo.class, null);
+
+ static final Entry rsaCert = new Entry("rsaCert",
+ """
+ -----BEGIN CERTIFICATE-----
+ MIIErDCCApQCCQD7ndjWbI/x0DANBgkqhkiG9w0BAQsFADAXMRUwEwYDVQQDDAxQ
+ RU0gVGVzdCBSU0EwIBcNMjQwMTA5MjMzNDIwWhgPMjA1MTA1MjYyMzM0MjBaMBcx
+ FTATBgNVBAMMDFBFTSBUZXN0IFJTQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCC
+ AgoCggIBAKgO/Pciro8xn5iNjcVCR4IuXP+V1PNATtKAlMbWzwGVOupKgRcNeRbA
+ N9RlljxSgEChIWs0/DB9VsAw1wCIVeuIVxv0ZvhVAcuD8Yyl58eev1rptsSJhTkN
+ YJFxEPSP2kfWDxS21ltbg1bnY/c1SQbzWawDLJN16G+ICzQXo68UB5fCZV9Ugfgf
+ 9USPkCiC6aFt+RT7eQaN/JrjtCm+mFf4VbK7jYW7D8AfjviEY1HQCnPoTjHBxdy+
+ o5s4aIOx1Wuu9wMoGuLXgY3do5/OSDCfByk7rc1drQB9GOKf2gkR8PL9TjK+R3Lq
+ wCA0a3jlCBiGPlH3oeZJrnp7jhAh/tVxbsd7yIdhQnasbiTfhew132AdPXoQE+ic
+ PFoh8MMtG1bdzt8EbvePC3GOjeyIP6f2Ixrh3B6wXzzYmJqBwON+X8TLQolcI1pa
+ Q7AUz5BScy3lO9nyJE/FJkX+Mr6n7WCdudCrQNP+0M845UvkgFyf4FcM7uUVugBm
+ AXy7sCqZgTeLdqHyTElMCoWzBa3MHKyiSCh8GUJH+I1yBY1gG95j3tITIOFvbZrk
+ vDiMwNtV9T6Ta2mb0+38GfKjbI6PF4DVrzB6xc7Q6/GwyhOb86YLOLlEHJfhuc+C
+ Pdy8hQrrulm2jiCO/skvHucABNJ2CENyWa7ljNJkcN6GNTziz4AhAgMBAAEwDQYJ
+ KoZIhvcNAQELBQADggIBAKFQE2AgYgc7/xzwveUAiZ55tfcds07UnazLCOdpz+JJ
+ W4MOt/1Qi9mUylqDEymfNZVLPd2dEjB4wJ57XBUjL+kXkH1SocuskxQPf05iz5zT
+ pEwg2fTmU73ilKMs5Q113nBnL9ZZtlRKCh1Oc5LvLW799uVXnU4UdSpWOBU9ePGY
+ +H1wUKf+e0/BkveQsZERYcamH9O9U/+h+bbhr3GpT1AVnuDRyF28OvRwARDCOVyy
+ ifh+xCR3WCnNcgfwCoH6cE1aXDKHchlAAZtvjc1lLud7/ECIg+15keVfTYk4HEbH
+ j/lprxyH7y99lMmRLQpnTve54RrZGGmg51UD7OmwPHLMGibfQkw6QgdNsggIYD6p
+ L91spgRRB+i4PTovocndOMR2RYgQEelGNqv8MsoUC7oRNxPCHxIEGuUPH1Vf3jnk
+ mTHbVzpjy57UtfcYp1uBFDf8WoWO1Mi6oXRw2YQA1YSMm1+3ftphxydcbRuBlS7O
+ 6Iiqk6XlFG9Dpd2jjAQQzJGtnC0QDgGz6/KGp1bGEhRnOWju07eLWvPbyaX5zeSh
+ 8gOYV33zkPhziWJt4uFMFIi7N2DLEk5UVZv1KTLZlfPl55DRs7j/Sb4vKHpB17AO
+ meVknxVvifDVY0TIz57t28Accsk6ClBCxNPluPU/8YLGAZJYsdDXjGcndQ13s5G7
+ -----END CERTIFICATE-----
+ """, X509Certificate.class);
+
+ static final Entry ecCert = new Entry("ecCert",
+ """
+ -----BEGIN CERTIFICATE-----
+ MIIBFzCBvgIJAOGVk/ky59ojMAoGCCqGSM49BAMCMBMxETAPBgNVBAMMCFBFTSB0
+ ZXN0MCAXDTI0MDEwOTIzMzEwNloYDzIwNTEwNTI2MjMzMTA2WjATMREwDwYDVQQD
+ DAhQRU0gdGVzdDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABGYI0jD7JZzw4RYD
+ y9DCfaYNz0CHrpr9gJU5NXe6czvuNBdAOl/lJGQ1pqpEQSQaMDII68obvQyQQyFY
+ lU3G9QAwCgYIKoZIzj0EAwIDSAAwRQIgMwYld7aBzkcRt9mn27YOed5+n0xN1y8Q
+ VEcFjLI/tBYCIQDU3szDZ/PK2mUZwtgQxLqHdh+f1JY0UwQS6M8QUvoDHw==
+ -----END CERTIFICATE-----
+ """, X509Certificate.class);
+
+ // EC cert with explicit parameters -- Not currently supported by SunEC
+ static final String ecCertEX = """
+ -----BEGIN CERTIFICATE-----
+ MIICrDCCAjMCCQDKAlI7uc1CVDAKBggqhkjOPQQDAjATMREwDwYDVQQDDAhQRU0g
+ dGVzdDAgFw0yNDAxMDkyMzIxNTlaGA8yMDUxMDUyNjIzMjE1OVowEzERMA8GA1UE
+ AwwIUEVNIHRlc3QwggHMMIIBZAYHKoZIzj0CATCCAVcCAQEwPAYHKoZIzj0BAQIx
+ AP/////////////////////////////////////////+/////wAAAAAAAAAA////
+ /zB7BDD//////////////////////////////////////////v////8AAAAAAAAA
+ AP////wEMLMxL6fiPufkmI4Fa+P4LRkYHZxu/oFBEgMUCI9QE4daxlY5jYou0Z0q
+ hcjt0+wq7wMVAKM1kmqjGaJ6HQCJamdzpIJ6zaxzBGEEqofKIr6LBTeOscce8yCt
+ dG4dO2KLp5uYWfdB4IJUKjhVAvJdv1UpbDpUXjhydgq3NhfeSpYmLG9dnpi/kpLc
+ Kfj0Hb0omhR86doxE7XwuMAKYLHOHX6BnXpDHXyQ6g5fAjEA////////////////
+ ////////////////x2NNgfQ3Ld9YGg2ySLCneuzsGWrMxSlzAgEBA2IABO+IbTh6
+ WqyzmxdCeJ0uUQ2v2jKxRuCKRyPlYAnpBmmQypsRS+GBdbBa0Mu6MTnVJh5uvqXn
+ q7IuHVEiE3EFKw0DNW30nINuQg6lTv6PgN/4nYBqsl5FQgzk2SYN3bw+7jAKBggq
+ hkjOPQQDAgNnADBkAjATCnbbn3CgPRPi9Nym0hKpBAXc30D4eVB3mz8snK0oKU0+
+ VP3F0EWcyM2QDSZCXIgCMHWknAhIGFTHxqypYUV8eAd3SY7ujZ6EPR0uG//csBWG
+ IqHcgr8slqi35ycQn5yMsQ==
+ -----END CERTIFICATE-----
+ """;
+
+ static final Entry ecsecp384 = new Entry("ecsecp384",
+ """
+ -----BEGIN PRIVATE KEY-----
+ MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDBVS52ZSKZ0oES7twD2
+ GGwRIVu3uHlGIwlu0xzFe7sgIPntca2bHfYMhgGxrlCm0q+hZANiAAQNWgwWfLX8
+ 8pYVjvwbfvDF9f+Oa9w6JjrfpWwFAUI6b1OPgrNUh+yXtUXnQNXnfUcIu0Os53bM
+ 8fTqPkQl6RyWEDHeXqJK8zTBHMeBq9nLfDPSbzQgLDyC64Orn0D8exM=
+ -----END PRIVATE KEY-----
+ """, KeyPair.class);
+
+ public static final Entry ecCSR = new Entry("ecCSR",
+ """
+ -----BEGIN CERTIFICATE REQUEST-----
+ MIICCTCCAbACAQAwRTELMAkGA1UEBhMCVVMxDTALBgNVBAgMBFRlc3QxFDASBgNV
+ BAcMC1NhbnRhIENsYXJhMREwDwYDVQQDDAhUZXN0IENTUjCCAUswggEDBgcqhkjO
+ PQIBMIH3AgEBMCwGByqGSM49AQECIQD/////AAAAAQAAAAAAAAAAAAAAAP//////
+ /////////zBbBCD/////AAAAAQAAAAAAAAAAAAAAAP///////////////AQgWsY1
+ 2Ko6k+ez671VdpiGvGUdBrDMU7D2O848PifSYEsDFQDEnTYIhucEk2pmeOETnSa3
+ gZ9+kARBBGsX0fLhLEJH+Lzm5WOkQPJ3A32BLeszoPShOUXYmMKWT+NC4v4af5uO
+ 5+tKfA+eFivOM1drMV7Oy7ZAaDe/UfUCIQD/////AAAAAP//////////vOb6racX
+ noTzucrC/GMlUQIBAQNCAAT3UJgGXD7xMwFSzBnkhsEXz3eJLjIE0HTP1Ax6x7QX
+ G3/+Z/qgOZ6UQCxeHOWMEgF1Ufc/tZkzgbvxWJ6gokeToBUwEwYJKoZIhvcNAQkH
+ MQYMBGZpc2gwCgYIKoZIzj0EAwIDRwAwRAIgUBTdrMDE4BqruYRh1rRyKQBf48WR
+ kIX8R4dBK9h1VRcCIEBR2Mzvku/huTbWTwKVlXBZeEmwIlxKwpRepPtViXcW
+ -----END CERTIFICATE REQUEST-----
+ """, PEMRecord.class);
+
+ public static final String preData = "TEXT BLAH TEXT BLAH" +
+ System.lineSeparator();
+ public static final String postData = "FINISHED" + System.lineSeparator();
+
+ public static final Entry ecCSRWithData = new Entry("ecCSRWithData",
+ preData + """
+ -----BEGIN CERTIFICATE REQUEST-----
+ MIICCTCCAbACAQAwRTELMAkGA1UEBhMCVVMxDTALBgNVBAgMBFRlc3QxFDASBgNV
+ BAcMC1NhbnRhIENsYXJhMREwDwYDVQQDDAhUZXN0IENTUjCCAUswggEDBgcqhkjO
+ PQIBMIH3AgEBMCwGByqGSM49AQECIQD/////AAAAAQAAAAAAAAAAAAAAAP//////
+ /////////zBbBCD/////AAAAAQAAAAAAAAAAAAAAAP///////////////AQgWsY1
+ 2Ko6k+ez671VdpiGvGUdBrDMU7D2O848PifSYEsDFQDEnTYIhucEk2pmeOETnSa3
+ gZ9+kARBBGsX0fLhLEJH+Lzm5WOkQPJ3A32BLeszoPShOUXYmMKWT+NC4v4af5uO
+ 5+tKfA+eFivOM1drMV7Oy7ZAaDe/UfUCIQD/////AAAAAP//////////vOb6racX
+ noTzucrC/GMlUQIBAQNCAAT3UJgGXD7xMwFSzBnkhsEXz3eJLjIE0HTP1Ax6x7QX
+ G3/+Z/qgOZ6UQCxeHOWMEgF1Ufc/tZkzgbvxWJ6gokeToBUwEwYJKoZIhvcNAQkH
+ MQYMBGZpc2gwCgYIKoZIzj0EAwIDRwAwRAIgUBTdrMDE4BqruYRh1rRyKQBf48WR
+ kIX8R4dBK9h1VRcCIEBR2Mzvku/huTbWTwKVlXBZeEmwIlxKwpRepPtViXcW
+ -----END CERTIFICATE REQUEST-----
+ """ + postData, PEMRecord.class);
+
+ final static Pattern CR = Pattern.compile("\r");
+ final static Pattern LF = Pattern.compile("\n");
+ final static Pattern LSDEFAULT = Pattern.compile(System.lineSeparator());
+
+
+ public record Entry(String name, String pem, Class clazz, char[] password,
+ byte[] der) {
+
+ public Entry(String name, String pem, Class clazz, char[] password,
+ byte[] der) {
+ this.name = name;
+ this.pem = pem;
+ this.clazz = clazz;
+ this.password = password;
+ if (pem != null && pem.length() > 0) {
+ String[] pemtext = pem.split("-----");
+ this.der = Base64.getMimeDecoder().decode(pemtext[2]);
+ } else {
+ this.der = null;
+ }
+ }
+ Entry(String name, String pem, Class clazz, char[] password) {
+ this(name, pem, clazz, password, null);
+ }
+
+ Entry(String name, String pem, Class clazz) {
+ this(name, pem, clazz, null, null);
+ }
+
+ public Entry newClass(String name, Class c) {
+ return new Entry(name, pem, c, password);
+ }
+
+ public Entry newClass(Class c) {
+ return newClass(name, c);
+ }
+
+ Entry makeCRLF(String name) {
+ return new Entry(name,
+ Pattern.compile(System.lineSeparator()).matcher(pem).replaceAll("\r\n"),
+ clazz, password());
+ }
+
+ Entry makeCR(String name) {
+ return new Entry(name,
+ Pattern.compile(System.lineSeparator()).matcher(pem).replaceAll("\r"),
+ clazz, password());
+ }
+
+ Entry makeNoCRLF(String name) {
+ return new Entry(name,
+ LF.matcher(CR.matcher(pem).replaceAll("")).
+ replaceAll(""),
+ clazz, password());
+ }
+ }
+
+ static public Entry getEntry(String varname) {
+ return getEntry(passList, varname);
+ }
+
+ static public Entry getEntry(List