8298420: Implement JEP 470: PEM Encodings of Cryptographic Objects (Preview)

Reviewed-by: weijun, mr, mullan, jnimeh
This commit is contained in:
Anthony Scarpino 2025-05-28 19:52:18 +00:00
parent 28f509317d
commit bb2c80c0e9
39 changed files with 3518 additions and 401 deletions

View File

@ -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 @@ import java.security.spec.AlgorithmParameterSpec;
*
* @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

View File

@ -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 {
}

View File

@ -37,7 +37,7 @@ package java.security;
* @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;

View File

@ -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.
*
* <p> The {@linkplain #decode(String)} and {@linkplain #decode(InputStream)}
* methods return an instance of a class that matches the data
* type and implements {@link DEREncodable}.
*
* <p> The following lists the supported PEM types and the {@code DEREncodable}
* types that each are decoded as:
* <ul>
* <li>CERTIFICATE : {@code X509Certificate}</li>
* <li>X509 CRL : {@code X509CRL}</li>
* <li>PUBLIC KEY : {@code PublicKey}</li>
* <li>PUBLIC KEY : {@code X509EncodedKeySpec} (Only supported when passed as
* a {@code Class} parameter)</li>
* <li>PRIVATE KEY : {@code PrivateKey}</li>
* <li>PRIVATE KEY : {@code PKCS8EncodedKeySpec} (Only supported when passed
* as a {@code Class} parameter)</li>
* <li>PRIVATE KEY : {@code KeyPair} (if the encoding also contains a
* public key)</li>
* <li>ENCRYPTED PRIVATE KEY : {@code EncryptedPrivateKeyInfo} </li>
* <li>ENCRYPTED PRIVATE KEY : {@code PrivateKey} (if configured with
* Decryption)</li>
* <li>Other types : {@code PEMRecord} </li>
* </ul>
*
* <p> 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.
*
* <p> If the PEM type does not have a corresponding class,
* {@code decode(String)} and {@code decode(InputStream)} will return a
* {@link PEMRecord}.
*
* <p> 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.
*
* <p> 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.
*
* <p>This class is immutable and thread-safe.
*
* <p> Here is an example of decoding a {@code PrivateKey} object:
* {@snippet lang = java:
* PEMDecoder pd = PEMDecoder.of();
* PrivateKey priKey = pd.decode(priKeyPEM, PrivateKey.class);
* }
*
* <p> 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}.
*
* <p> 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.
*
* <p> 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}.
*
* <p> 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}.
*
* <p> 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}.
*
* <p> 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}.
*
* <p> 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.
*
* <p> 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.
*
* <p> 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.
*
* <p> Input consumed by this method is read in as
* {@link java.nio.charset.StandardCharsets#UTF_8 UTF-8}.
*
* @param <S> 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 extends DEREncodable> S decode(String str, Class<S> 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.
*
* <p> 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}.
*
* <p> 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.
*
* <p> If no PEM data is found, an {@code IllegalArgumentException} is
* thrown.
*
* @param <S> 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 extends DEREncodable> S decode(InputStream is, Class<S> 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.
*
* <p>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));
}
}

View File

@ -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.
*
* <p> 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.
*
* <p> 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.
*
* <p> 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.
*
* <p> 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.
*
* <p>The following lists the supported {@code DEREncodable} classes and
* the PEM types that each are encoded as:
*
* <ul>
* <li>{@code X509Certificate} : CERTIFICATE</li>
* <li>{@code X509CRL} : X509 CRL</li>
* <li>{@code PublicKey}: PUBLIC KEY</li>
* <li>{@code PrivateKey} : PRIVATE KEY</li>
* <li>{@code PrivateKey} (if configured with encryption):
* ENCRYPTED PRIVATE KEY</li>
* <li>{@code EncryptedPrivateKeyInfo} : ENCRYPTED PRIVATE KEY</li>
* <li>{@code KeyPair} : PRIVATE KEY</li>
* <li>{@code X509EncodedKeySpec} : PUBLIC KEY</li>
* <li>{@code PKCS8EncodedKeySpec} : PRIVATE KEY</li>
* <li>{@code PEMRecord} : {@code PEMRecord.type()}</li>
* </ul>
*
* <p> This class is immutable and thread-safe.
*
* <p> Here is an example of encoding a {@code PrivateKey} object:
* {@snippet lang = java:
* PEMEncoder pe = PEMEncoder.of();
* byte[] pemData = pe.encode(privKey);
* }
*
* <p> 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.
*
* <p> 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);
}
}

View File

@ -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}.
*
* <p> {@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.
*
* <p> 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);
}
}

View File

@ -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 @@ import java.util.Set;
* @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;

View File

@ -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 @@ import java.util.List;
* @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;

View File

@ -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:
*
* <pre>
* PrivateKeyInfo ::= SEQUENCE {
* OneAsymmetricKey ::= SEQUENCE {
* version Version,
* privateKeyAlgorithm PrivateKeyAlgorithmIdentifier,
* privateKey PrivateKey,
* attributes [0] IMPLICIT Attributes OPTIONAL }
* attributes [0] Attributes OPTIONAL,
* ...,
* [[2: publicKey [1] PublicKey OPTIONAL ]],
* ...
* }
*
* Version ::= INTEGER
* PrivateKeyInfo ::= OneAsymmetricKey
*
* Version ::= INTEGER { v1(0), v2(1) }
*
* PrivateKeyAlgorithmIdentifier ::= AlgorithmIdentifier
*
* PrivateKey ::= OCTET STRING
*
* PublicKey ::= BIT STRING
*
* Attributes ::= SET OF Attribute
* </pre>
*
@ -56,11 +66,14 @@ package java.security.spec;
* @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.
*

View File

@ -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 @@ package java.security.spec;
* @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.
*

View File

@ -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 @@ import sun.security.util.DerOutputStream;
* @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 class EncryptedPrivateKeyInfo {
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 class EncryptedPrivateKeyInfo {
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.
* <p>Note: Standard name is returned instead of the specified one
@ -308,6 +320,242 @@ public class EncryptedPrivateKeyInfo {
}
}
/**
* Creates and encrypts an {@code EncryptedPrivateKeyInfo} from a given
* {@code PrivateKey}. A valid password-based encryption (PBE) algorithm
* and password must be specified.
*
* <p> The PBE algorithm string format details can be found in the
* <a href="{@docRoot}/../specs/security/standard-names.html#cipher-algorithms">
* Cipher section</a> 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 class EncryptedPrivateKeyInfo {
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 class EncryptedPrivateKeyInfo {
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 @@ public class EncryptedPrivateKeyInfo {
}
}
@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));
}
}

View File

@ -78,6 +78,8 @@ public @interface PreviewFeature {
KEY_DERIVATION, //remove when the boot JDK is JDK 25
@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.

View File

@ -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 final class ECKeyFactory extends KeyFactorySpi {
* 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 @@ public final class ECKeyFactory extends KeyFactorySpi {
// see JCA doc
protected PublicKey engineGeneratePublic(KeySpec keySpec)
throws InvalidKeySpecException {
throws InvalidKeySpecException {
try {
return implGeneratePublic(keySpec);
} catch (InvalidKeySpecException e) {
@ -159,7 +160,7 @@ public final class ECKeyFactory extends KeyFactorySpi {
// see JCA doc
protected PrivateKey engineGeneratePrivate(KeySpec keySpec)
throws InvalidKeySpecException {
throws InvalidKeySpecException {
try {
return implGeneratePrivate(keySpec);
} catch (InvalidKeySpecException e) {
@ -171,19 +172,13 @@ public final class ECKeyFactory extends KeyFactorySpi {
// 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 @@ public final class ECKeyFactory extends KeyFactorySpi {
// 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 @@ public final class ECKeyFactory extends KeyFactorySpi {
} 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 <T extends KeySpec> T engineGetKeySpec(Key key, Class<T> keySpec)
throws InvalidKeySpecException {
throws InvalidKeySpecException {
try {
// convert key to one of our keys
// this also verifies that the key is a valid EC key and ensures
@ -263,8 +255,7 @@ public final class ECKeyFactory extends KeyFactorySpi {
} catch (InvalidKeyException e) {
throw new InvalidKeySpecException(e);
}
if (key instanceof ECPublicKey) {
ECPublicKey ecKey = (ECPublicKey)key;
if (key instanceof ECPublicKey ecKey) {
if (keySpec.isAssignableFrom(ECPublicKeySpec.class)) {
return keySpec.cast(new ECPublicKeySpec(
ecKey.getW(),

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2006, 2023, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2006, 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
@ -40,6 +40,7 @@ import sun.security.ec.point.MutablePoint;
import sun.security.util.*;
import sun.security.x509.AlgorithmId;
import sun.security.pkcs.PKCS8Key;
import sun.security.x509.X509Key;
/**
* Key implementation for EC private keys.
@ -73,6 +74,7 @@ public final class ECPrivateKeyImpl extends PKCS8Key implements ECPrivateKey {
private byte[] arrayS; // private value as a little-endian array
@SuppressWarnings("serial") // Type of field is not Serializable
private ECParameterSpec params;
private byte[] domainParams; //Currently unsupported
/**
* Construct a key from its encoding. Called by the ECKeyFactory.
@ -111,7 +113,7 @@ public final class ECPrivateKeyImpl extends PKCS8Key implements ECPrivateKey {
out.putOctetString(privBytes);
Arrays.fill(privBytes, (byte) 0);
DerValue val = DerValue.wrap(DerValue.tag_Sequence, out);
key = val.toByteArray();
privKeyMaterial = val.toByteArray();
val.clear();
}
@ -133,7 +135,7 @@ public final class ECPrivateKeyImpl extends PKCS8Key implements ECPrivateKey {
out.putOctetString(sOctets);
Arrays.fill(sOctets, (byte) 0);
DerValue val = DerValue.wrap(DerValue.tag_Sequence, out);
key = val.toByteArray();
privKeyMaterial = val.toByteArray();
val.clear();
}
@ -153,64 +155,78 @@ public final class ECPrivateKeyImpl extends PKCS8Key implements ECPrivateKey {
return s;
}
private byte[] getArrayS0() {
// Return the internal arrayS byte[], if arrayS is null generate it.
public byte[] getArrayS() {
if (arrayS == null) {
arrayS = ECUtil.sArray(getS(), params);
}
return arrayS;
}
public byte[] getArrayS() {
return getArrayS0().clone();
}
// see JCA doc
public ECParameterSpec getParams() {
return params;
}
/**
* Parse the ASN.1 of the privateKey Octet
*/
private void parseKeyBits() throws InvalidKeyException {
// Parse private key material from PKCS8Key.decode()
try {
DerInputStream in = new DerInputStream(key);
DerInputStream in = new DerInputStream(privKeyMaterial);
DerValue derValue = in.getDerValue();
if (derValue.tag != DerValue.tag_Sequence) {
throw new IOException("Not a SEQUENCE");
}
DerInputStream data = derValue.data;
int version = data.getInteger();
if (version != 1) {
if (version != V2) {
throw new IOException("Version must be 1");
}
byte[] privData = data.getOctetString();
ArrayUtil.reverse(privData);
arrayS = privData;
while (data.available() != 0) {
DerValue value = data.getDerValue();
if (value.isContextSpecific((byte) 0)) {
// ignore for now
} else if (value.isContextSpecific((byte) 1)) {
// ignore for now
} else {
throw new InvalidKeyException("Unexpected value: " + value);
}
}
// Validate parameters stored from PKCS8Key.decode()
AlgorithmParameters algParams = this.algid.getParameters();
if (algParams == null) {
throw new InvalidKeyException("EC domain parameters must be "
+ "encoded in the algorithm identifier");
}
params = algParams.getParameterSpec(ECParameterSpec.class);
if (data.available() == 0) {
return;
}
DerValue value = data.getDerValue();
if (value.isContextSpecific((byte) 0)) {
domainParams = value.getDataBytes(); // Save DER sequence
if (data.available() == 0) {
return;
}
value = data.getDerValue();
}
if (value.isContextSpecific((byte) 1)) {
DerValue bits = value.withTag(DerValue.tag_BitString);
pubKeyEncoded = new X509Key(algid,
bits.data.getUnalignedBitString()).getEncoded();
} else {
throw new InvalidKeyException("Unexpected value: " + value);
}
} catch (IOException | InvalidParameterSpecException e) {
throw new InvalidKeyException("Invalid EC private key", e);
}
}
@Override
public PublicKey calculatePublicKey() {
ECParameterSpec ecParams = getParams();
ECOperations ops = ECOperations.forParameters(ecParams)
.orElseThrow(ProviderException::new);
MutablePoint pub = ops.multiply(ecParams.getGenerator(), getArrayS0());
MutablePoint pub = ops.multiply(ecParams.getGenerator(), getArrayS());
AffinePoint affPub = pub.asAffine();
ECPoint w = new ECPoint(affPub.getX().asBigInteger(),
affPub.getY().asBigInteger());

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2018, 2021, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2018, 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,12 +25,9 @@
package sun.security.ec;
import java.security.KeyFactorySpi;
import java.security.Key;
import java.security.PublicKey;
import java.security.PrivateKey;
import java.security.InvalidKeyException;
import java.security.ProviderException;
import sun.security.pkcs.PKCS8Key;
import java.security.*;
import java.security.interfaces.XECKey;
import java.security.interfaces.XECPrivateKey;
import java.security.interfaces.XECPublicKey;
@ -160,9 +157,19 @@ public class XDHKeyFactory extends KeyFactorySpi {
InvalidKeySpecException::new, publicKeySpec.getParams());
checkLockedParams(InvalidKeySpecException::new, params);
return new XDHPublicKeyImpl(params, publicKeySpec.getU());
} else if (keySpec instanceof PKCS8EncodedKeySpec p8) {
PKCS8Key p8key = new XDHPrivateKeyImpl(p8.getEncoded());
if (!p8key.hasPublicKey()) {
throw new InvalidKeySpecException("No public key found.");
}
XDHPublicKeyImpl result =
new XDHPublicKeyImpl(p8key.getPubKeyEncoded());
checkLockedParams(InvalidKeySpecException::new,
result.getParams());
return result;
} else {
throw new InvalidKeySpecException(
"Only X509EncodedKeySpec and XECPublicKeySpec are supported");
throw new InvalidKeySpecException(keySpec.getClass().getName() +
" not supported.");
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2018, 2023, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2018, 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
@ -54,7 +54,7 @@ public final class XDHPrivateKeyImpl extends PKCS8Key implements XECPrivateKey {
DerValue val = new DerValue(DerValue.tag_OctetString, k);
try {
this.key = val.toByteArray();
this.privKeyMaterial = val.toByteArray();
} finally {
val.clear();
}
@ -67,7 +67,7 @@ public final class XDHPrivateKeyImpl extends PKCS8Key implements XECPrivateKey {
InvalidKeyException::new, algid);
paramSpec = new NamedParameterSpec(params.getName());
try {
DerInputStream derStream = new DerInputStream(key);
DerInputStream derStream = new DerInputStream(privKeyMaterial);
k = derStream.getOctetString();
} catch (IOException ex) {
throw new InvalidKeyException(ex);
@ -102,7 +102,6 @@ public final class XDHPrivateKeyImpl extends PKCS8Key implements XECPrivateKey {
return Optional.of(getK());
}
@Override
public PublicKey calculatePublicKey() {
XECParameters params = paramSpec.getName().equalsIgnoreCase("X25519")
? XECParameters.X25519

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020, 2021, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2020, 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,12 +25,9 @@
package sun.security.ec.ed;
import java.security.KeyFactorySpi;
import java.security.Key;
import java.security.PublicKey;
import java.security.PrivateKey;
import java.security.InvalidKeyException;
import java.security.ProviderException;
import sun.security.pkcs.PKCS8Key;
import java.security.*;
import java.security.interfaces.*;
import java.security.spec.*;
import java.util.Arrays;
@ -153,9 +150,15 @@ public class EdDSAKeyFactory extends KeyFactorySpi {
InvalidKeySpecException::new, publicKeySpec.getParams());
checkLockedParams(InvalidKeySpecException::new, params);
return new EdDSAPublicKeyImpl(params, publicKeySpec.getPoint());
} else if (keySpec instanceof PKCS8EncodedKeySpec p8) {
PKCS8Key p8key = new EdDSAPrivateKeyImpl(p8.getEncoded());
if (!p8key.hasPublicKey()) {
throw new InvalidKeySpecException("No public key found.");
}
return new EdDSAPublicKeyImpl(p8key.getPubKeyEncoded());
} else {
throw new InvalidKeySpecException(
"Only X509EncodedKeySpec and EdECPublicKeySpec are supported");
throw new InvalidKeySpecException(keySpec.getClass().getName() +
" not supported.");
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020, 2023, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2020, 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
@ -56,7 +56,7 @@ public final class EdDSAPrivateKeyImpl
DerValue val = new DerValue(DerValue.tag_OctetString, h);
try {
this.key = val.toByteArray();
privKeyMaterial = val.toByteArray();
} finally {
val.clear();
}
@ -71,7 +71,7 @@ public final class EdDSAPrivateKeyImpl
paramSpec = new NamedParameterSpec(params.getName());
try {
DerInputStream derStream = new DerInputStream(key);
DerInputStream derStream = new DerInputStream(privKeyMaterial);
h = derStream.getOctetString();
} catch (IOException ex) {
throw new InvalidKeyException(ex);
@ -81,8 +81,8 @@ public final class EdDSAPrivateKeyImpl
void checkLength(EdDSAParameters params) throws InvalidKeyException {
if (params.getKeyLength() != this.h.length) {
throw new InvalidKeyException("key length is " + this.h.length +
if (params.getKeyLength() != h.length) {
throw new InvalidKeyException("key length is " + h.length +
", key length must be " + params.getKeyLength());
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2024, 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
@ -75,7 +75,7 @@ public final class NamedPKCS8Key extends PKCS8Key {
DerValue val = new DerValue(DerValue.tag_OctetString, rawBytes);
try {
this.key = val.toByteArray();
this.privKeyMaterial = val.toByteArray();
} finally {
val.clear();
}
@ -90,7 +90,7 @@ public final class NamedPKCS8Key extends PKCS8Key {
if (algid.getEncodedParams() != null) {
throw new InvalidKeyException("algorithm identifier has params");
}
rawBytes = new DerInputStream(key).getOctetString();
rawBytes = new DerInputStream(privKeyMaterial).getOctetString();
} catch (IOException e) {
throw new InvalidKeyException("Cannot parse input", e);
}
@ -129,7 +129,7 @@ public final class NamedPKCS8Key extends PKCS8Key {
@Override
public void destroy() throws DestroyFailedException {
Arrays.fill(rawBytes, (byte)0);
Arrays.fill(key, (byte)0);
Arrays.fill(privKeyMaterial, (byte)0);
if (encodedKey != null) {
Arrays.fill(encodedKey, (byte)0);
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 1996, 2023, 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
@ -25,22 +25,18 @@
package sun.security.pkcs;
import java.io.*;
import java.security.Key;
import java.security.KeyRep;
import java.security.PrivateKey;
import java.security.KeyFactory;
import java.security.MessageDigest;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import jdk.internal.access.SharedSecrets;
import sun.security.util.*;
import sun.security.x509.AlgorithmId;
import sun.security.x509.X509Key;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Arrays;
import jdk.internal.access.SharedSecrets;
import sun.security.x509.*;
import sun.security.util.*;
/**
* Holds a PKCS#8 key, for example a private key
*
@ -56,7 +52,7 @@ import sun.security.util.*;
* ...
* }
*
* We support this format but do not parse attributes and publicKey now.
* We support this format but do not parse attributes.
*/
public class PKCS8Key implements PrivateKey, InternalPrivateKey {
@ -67,20 +63,29 @@ public class PKCS8Key implements PrivateKey, InternalPrivateKey {
/* The algorithm information (name, parameters, etc). */
protected AlgorithmId algid;
/* The key bytes, without the algorithm information */
protected byte[] key;
/* The private key OctetString for the algorithm subclasses to decode */
protected byte[] privKeyMaterial;
/* The encoded for the key. Created on demand by encode(). */
/* The pkcs8 encoding of this key(s). Created on demand. */
protected byte[] encodedKey;
/* The encoded x509 public key for v2 */
protected byte[] pubKeyEncoded = null;
/* ASN.1 Attributes */
private byte[] attributes;
/* PKCS8 version of the PEM */
private int version;
/* The version for this key */
private static final int V1 = 0;
private static final int V2 = 1;
public static final int V1 = 0;
public static final int V2 = 1;
/**
* Default constructor. Constructors in subclasses that create a new key
* from its components require this. These constructors must initialize
* {@link #algid} and {@link #key}.
* {@link #algid} and {@link #privKeyMaterial}.
*/
protected PKCS8Key() { }
@ -91,7 +96,7 @@ public class PKCS8Key implements PrivateKey, InternalPrivateKey {
*
* This method is also used by {@link #parseKey} to create a raw key.
*/
protected PKCS8Key(byte[] input) throws InvalidKeyException {
public PKCS8Key(byte[] input) throws InvalidKeyException {
try {
decode(new DerValue(input));
} catch (IOException e) {
@ -99,39 +104,70 @@ public class PKCS8Key implements PrivateKey, InternalPrivateKey {
}
}
private PKCS8Key(byte[] privEncoding, byte[] pubEncoding)
throws InvalidKeyException {
this(privEncoding);
pubKeyEncoded = pubEncoding;
version = V2;
}
public int getVersion() {
return version;
}
/**
* Method for decoding PKCS8 v1 and v2 formats. Decoded values are stored
* in this class, key material remains in DER format for algorithm
* subclasses to decode.
*/
private void decode(DerValue val) throws InvalidKeyException {
try {
if (val.tag != DerValue.tag_Sequence) {
throw new InvalidKeyException("invalid key format");
}
int version = val.data.getInteger();
// Support check for V1, aka 0, and V2, aka 1.
version = val.data.getInteger();
if (version != V1 && version != V2) {
throw new InvalidKeyException("unknown version: " + version);
}
algid = AlgorithmId.parse (val.data.getDerValue ());
key = val.data.getOctetString();
// Parse and store AlgorithmID
algid = AlgorithmId.parse(val.data.getDerValue());
DerValue next;
// Store key material for subclasses to parse
privKeyMaterial = val.data.getOctetString();
// PKCS8 v1 typically ends here
if (val.data.available() == 0) {
return;
}
next = val.data.getDerValue();
if (next.isContextSpecific((byte)0)) {
// OPTIONAL Context tag 0 for Attributes for PKCS8 v1 & v2
// Uses 0xA0 context-specific/constructed or 0x80
// context-specific/primitive.
DerValue v = val.data.getDerValue();
if (v.isContextSpecific((byte)0)) {
attributes = v.getDataBytes(); // Save DER sequence
if (val.data.available() == 0) {
return;
}
next = val.data.getDerValue();
v = val.data.getDerValue();
}
if (next.isContextSpecific((byte)1)) {
if (version == V1) {
throw new InvalidKeyException("publicKey seen in v1");
// OPTIONAL context tag 1 for Public Key for PKCS8 v2 only
if (version == V2) {
if (v.isContextSpecific((byte)1)) {
DerValue bits = v.withTag(DerValue.tag_BitString);
pubKeyEncoded = new X509Key(algid,
bits.getUnalignedBitString()).getEncoded();
} else {
throw new InvalidKeyException("Invalid context tag");
}
if (val.data.available() == 0) {
return;
}
}
throw new InvalidKeyException("Extra bytes");
} catch (IOException e) {
throw new InvalidKeyException("Unable to decode key", e);
@ -154,17 +190,29 @@ public class PKCS8Key implements PrivateKey, InternalPrivateKey {
* handling, that specific need can be accommodated.
*
* @param encoded the DER-encoded SubjectPublicKeyInfo value
* @exception IOException on data format errors
* @exception InvalidKeyException on data format errors
*/
public static PrivateKey parseKey(byte[] encoded) throws IOException {
public static PrivateKey parseKey(byte[] encoded)
throws InvalidKeyException {
return parseKey(encoded, null);
}
public static PrivateKey parseKey(byte[] encoded, Provider provider)
throws InvalidKeyException {
try {
PKCS8Key rawKey = new PKCS8Key(encoded);
byte[] internal = rawKey.getEncodedInternal();
PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(internal);
PKCS8EncodedKeySpec pkcs8KeySpec =
new PKCS8EncodedKeySpec(rawKey.generateEncoding());
PrivateKey result = null;
try {
result = KeyFactory.getInstance(rawKey.algid.getName())
if (provider == null) {
result = KeyFactory.getInstance(rawKey.algid.getName())
.generatePrivate(pkcs8KeySpec);
} else {
result = KeyFactory.getInstance(rawKey.algid.getName(),
provider).generatePrivate(pkcs8KeySpec);
}
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
// Ignore and return raw key
result = rawKey;
@ -176,8 +224,8 @@ public class PKCS8Key implements PrivateKey, InternalPrivateKey {
.clearEncodedKeySpec(pkcs8KeySpec);
}
return result;
} catch (InvalidKeyException e) {
throw new IOException("corrupt private key", e);
} catch (IOException e) {
throw new InvalidKeyException(e);
}
}
@ -188,10 +236,18 @@ public class PKCS8Key implements PrivateKey, InternalPrivateKey {
return algid.getName();
}
public byte[] getPubKeyEncoded() {
return pubKeyEncoded;
}
public boolean hasPublicKey() {
return (pubKeyEncoded != null);
}
/**
* Returns the algorithm ID to be used with this key.
*/
public AlgorithmId getAlgorithmId () {
public AlgorithmId getAlgorithmId () {
return algid;
}
@ -210,6 +266,25 @@ public class PKCS8Key implements PrivateKey, InternalPrivateKey {
return "PKCS#8";
}
/**
* With a given encoded Public and Private key, generate and return a
* PKCS8v2 DER-encoded byte[].
*
* @param pubKeyEncoded DER-encoded PublicKey
* @param privKeyEncoded DER-encoded PrivateKey
* @return DER-encoded byte array
* @throws IOException thrown on encoding failure
*/
public static byte[] getEncoded(byte[] pubKeyEncoded, byte[] privKeyEncoded)
throws IOException {
try {
return new PKCS8Key(privKeyEncoded, pubKeyEncoded).
generateEncoding();
} catch (InvalidKeyException e) {
throw new IOException(e);
}
}
/**
* DER-encodes this key as a byte array stored inside this object
* and return it.
@ -218,17 +293,53 @@ public class PKCS8Key implements PrivateKey, InternalPrivateKey {
*/
private synchronized byte[] getEncodedInternal() {
if (encodedKey == null) {
DerOutputStream tmp = new DerOutputStream();
tmp.putInteger(V1);
algid.encode(tmp);
tmp.putOctetString(key);
DerValue out = DerValue.wrap(DerValue.tag_Sequence, tmp);
encodedKey = out.toByteArray();
out.clear();
try {
encodedKey = generateEncoding();
} catch (IOException e) {
return null;
}
}
return encodedKey;
}
private byte[] generateEncoding() throws IOException {
DerOutputStream out = new DerOutputStream();
out.putInteger(version);
algid.encode(out);
out.putOctetString(privKeyMaterial);
if (version == V2) {
if (attributes != null) {
out.writeImplicit(
DerValue.createTag((byte) (DerValue.TAG_CONTEXT |
DerValue.TAG_CONSTRUCT), false, (byte) 0),
new DerOutputStream().putOctetString(attributes));
}
if (pubKeyEncoded != null) {
X509Key x = new X509Key();
try {
x.decode(pubKeyEncoded);
} catch (InvalidKeyException e) {
throw new IOException(e);
}
// X509Key x = X509Key.parse(pubKeyEncoded);
DerOutputStream pubOut = new DerOutputStream();
pubOut.putUnalignedBitString(x.getKey());
out.writeImplicit(
DerValue.createTag(DerValue.TAG_CONTEXT, false,
(byte) 1), pubOut);
}
}
DerValue val = DerValue.wrap(DerValue.tag_Sequence, out);
encodedKey = val.toByteArray();
val.clear();
return encodedKey;
}
@java.io.Serial
protected Object writeReplace() throws java.io.ObjectStreamException {
return new KeyRep(KeyRep.Type.PRIVATE,
@ -298,6 +409,6 @@ public class PKCS8Key implements PrivateKey, InternalPrivateKey {
if (encodedKey != null) {
Arrays.fill(encodedKey, (byte)0);
}
Arrays.fill(key, (byte)0);
Arrays.fill(privKeyMaterial, (byte)0);
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 1996, 2022, 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
@ -70,7 +70,7 @@ public final class DSAPrivateKey extends PKCS8Key
byte[] xbytes = x.toByteArray();
DerValue val = new DerValue(DerValue.tag_Integer, xbytes);
key = val.toByteArray();
privKeyMaterial = val.toByteArray();
val.clear();
Arrays.fill(xbytes, (byte)0);
}
@ -81,7 +81,7 @@ public final class DSAPrivateKey extends PKCS8Key
public DSAPrivateKey(byte[] encoded) throws InvalidKeyException {
super(encoded);
try {
DerInputStream in = new DerInputStream(key);
DerInputStream in = new DerInputStream(privKeyMaterial);
x = in.getBigInteger();
} catch (IOException e) {
throw new InvalidKeyException(e.getMessage(), e);

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 1997, 2022, 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
@ -25,7 +25,6 @@
package sun.security.provider;
import java.io.IOException;
import java.security.SecureRandom;
import java.security.*;
import java.util.Arrays;
@ -300,8 +299,8 @@ final class KeyProtector {
// which in turn parses the key material.
try {
return PKCS8Key.parseKey(plainKey);
} catch (IOException ioe) {
throw new UnrecoverableKeyException(ioe.getMessage());
} catch (InvalidKeyException e) {
throw new UnrecoverableKeyException(e.getMessage());
} finally {
Arrays.fill(plainKey, (byte)0);
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 1998, 2022, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1998, 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
@ -27,6 +27,7 @@ package sun.security.provider;
import java.io.*;
import java.security.PEMRecord;
import java.security.cert.*;
import java.util.*;
@ -36,6 +37,7 @@ import sun.security.provider.certpath.X509CertPath;
import sun.security.provider.certpath.X509CertificatePair;
import sun.security.util.Cache;
import sun.security.util.DerValue;
import sun.security.util.Pem;
import sun.security.x509.X509CRLImpl;
import sun.security.x509.X509CertImpl;
@ -556,118 +558,20 @@ public class X509Factory extends CertificateFactorySpi {
readBERInternal(is, bout, c);
return bout.toByteArray();
} else {
// Read BASE64 encoded data, might skip info at the beginning
ByteArrayOutputStream data = new ByteArrayOutputStream();
// Step 1: Read until header is found
int hyphen = (c=='-') ? 1: 0; // count of consequent hyphens
int last = (c=='-') ? -1: c; // the char before hyphen
while (true) {
int next = is.read();
if (next == -1) {
// We accept useless data after the last block,
// say, empty lines.
try {
PEMRecord rec;
try {
rec = Pem.readPEM(is, (c == '-' ? true : false));
} catch (EOFException e) {
return null;
}
if (next == '-') {
hyphen++;
} else {
hyphen = 0;
last = next;
}
if (hyphen == 5 && (last == -1 || last == '\r' || last == '\n')) {
break;
}
}
// Step 2: Read the rest of header, determine the line end
int end;
StringBuilder header = new StringBuilder("-----");
while (true) {
int next = is.read();
if (next == -1) {
throw new IOException("Incomplete data");
}
if (next == '\n') {
end = '\n';
break;
}
if (next == '\r') {
next = is.read();
if (next == -1) {
throw new IOException("Incomplete data");
}
if (next == '\n') {
end = '\n';
} else {
end = '\r';
// Skip all white space chars
if (next != 9 && next != 10 && next != 13 && next != 32) {
data.write(next);
}
}
break;
}
header.append((char)next);
}
// Step 3: Read the data
while (true) {
int next = is.read();
if (next == -1) {
throw new IOException("Incomplete data");
}
if (next != '-') {
// Skip all white space chars
if (next != 9 && next != 10 && next != 13 && next != 32) {
data.write(next);
}
} else {
break;
}
}
// Step 4: Consume the footer
StringBuilder footer = new StringBuilder("-");
while (true) {
int next = is.read();
// Add next == '\n' for maximum safety, in case endline
// is not consistent.
if (next == -1 || next == end || next == '\n') {
break;
}
if (next != '\r') footer.append((char)next);
}
checkHeaderFooter(header.toString().stripTrailing(),
footer.toString().stripTrailing());
try {
return Base64.getDecoder().decode(data.toByteArray());
return Base64.getDecoder().decode(rec.pem());
} catch (IllegalArgumentException e) {
throw new IOException(e);
}
}
}
private static void checkHeaderFooter(String header,
String footer) throws IOException {
if (header.length() < 16 || !header.startsWith("-----BEGIN ") ||
!header.endsWith("-----")) {
throw new IOException("Illegal header: " + header);
}
if (footer.length() < 14 || !footer.startsWith("-----END ") ||
!footer.endsWith("-----")) {
throw new IOException("Illegal footer: " + footer);
}
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: " +
header + " " + footer);
}
}
/**
* Read one BER data block. This method is aware of indefinite-length BER
* encoding and will read all the subsections in a recursive way

View File

@ -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
@ -32,6 +32,7 @@ import java.security.interfaces.*;
import java.security.spec.*;
import java.util.Arrays;
import sun.security.pkcs.PKCS8Key;
import sun.security.rsa.RSAUtil.KeyType;
/**
@ -332,9 +333,15 @@ public class RSAKeyFactory extends KeyFactorySpi {
} catch (ProviderException e) {
throw new InvalidKeySpecException(e);
}
} else if (keySpec instanceof PKCS8EncodedKeySpec p8) {
PKCS8Key p8key = new PKCS8Key(p8.getEncoded());
if (!p8key.hasPublicKey()) {
throw new InvalidKeySpecException("No public key found.");
}
return RSAPublicKeyImpl.newKey(type, "X.509",
p8key.getPubKeyEncoded());
} else {
throw new InvalidKeySpecException("Only RSAPublicKeySpec "
+ "and X509EncodedKeySpec supported for RSA public keys");
throw new InvalidKeySpecException(keySpec.getClass().getName() + " not supported.");
}
}

View File

@ -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
@ -69,6 +69,7 @@ public final class RSAPrivateCrtKeyImpl
private BigInteger qe; // prime exponent q
private BigInteger coeff; // CRT coefficient
// RSA or RSA-PSS KeyType
private final transient KeyType type;
// Optional parameters associated with this RSA key
@ -101,7 +102,7 @@ public final class RSAPrivateCrtKeyImpl
}
case "PKCS#1":
try {
BigInteger[] comps = parseASN1(encoded);
BigInteger[] comps = parsePKCS1(encoded);
if ((comps[1].signum() == 0) || (comps[3].signum() == 0) ||
(comps[4].signum() == 0) || (comps[5].signum() == 0) ||
(comps[6].signum() == 0) || (comps[7].signum() == 0)) {
@ -237,7 +238,7 @@ public final class RSAPrivateCrtKeyImpl
Arrays.fill(nbytes[6], (byte) 0);
Arrays.fill(nbytes[7], (byte) 0);
DerValue val = DerValue.wrap(DerValue.tag_Sequence, out);
key = val.toByteArray();
privKeyMaterial = val.toByteArray();
val.clear();
}
@ -304,7 +305,7 @@ public final class RSAPrivateCrtKeyImpl
// utility method for parsing DER encoding of RSA private keys in PKCS#1
// format as defined in RFC 8017 Appendix A.1.2, i.e. SEQ of version, n,
// e, d, p, q, pe, qe, and coeff, and return the parsed components.
private static BigInteger[] parseASN1(byte[] raw) throws IOException {
private static BigInteger[] parsePKCS1(byte[] raw) throws IOException {
DerValue derValue = new DerValue(raw);
try {
if (derValue.tag != DerValue.tag_Sequence) {
@ -337,7 +338,7 @@ public final class RSAPrivateCrtKeyImpl
private void parseKeyBits() throws InvalidKeyException {
try {
BigInteger[] comps = parseASN1(key);
BigInteger[] comps = parsePKCS1(privKeyMaterial);
n = comps[0];
e = comps[1];
d = comps[2];
@ -351,6 +352,30 @@ public final class RSAPrivateCrtKeyImpl
}
}
/**
* With a given PKCS#1/slleay/OpenSSL old default RSA binary encoding,
* decode and return the proper RSA encoded KeySpec
* @param encoded RSA binary encoding
* @return KeySpec
* @throws InvalidKeyException on decoding failure
*/
public static KeySpec getKeySpec(byte[] encoded) throws
InvalidKeyException {
try {
BigInteger[] comps = parsePKCS1(encoded);
if ((comps[1].signum() == 0) || (comps[3].signum() == 0) ||
(comps[4].signum() == 0) || (comps[5].signum() == 0) ||
(comps[6].signum() == 0) || (comps[7].signum() == 0)) {
return new RSAPrivateKeySpec(comps[0], comps[2]);
} else {
return new RSAPrivateCrtKeySpec(comps[0],
comps[1], comps[2], comps[3], comps[4], comps[5],
comps[6], comps[7]);
}
} catch (IOException ioe) {
throw new InvalidKeyException("Invalid PKCS#1 encoding", ioe);
}
}
/**
* Restores the state of this object from the stream.
* <p>

View File

@ -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();
}

View File

@ -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 final class RSAPublicKeyImpl extends X509Key implements RSAPublicKey {
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 final class RSAPublicKeyImpl extends X509Key implements RSAPublicKey {
// 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 @@ public final class RSAPublicKeyImpl extends X509Key implements RSAPublicKey {
*/
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) {

View File

@ -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;

View File

@ -41,6 +41,7 @@ import javax.security.auth.DestroyFailedException;
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 final class KeyUtil {
}
}
}
/**
* 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");
}
}

View File

@ -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);
}
}

View File

@ -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.InvalidKeyException;
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 class X509Key implements PublicKey, DerEncoder {
* 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 @@ public class X509Key implements PublicKey, DerEncoder {
* 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 class X509Key implements PublicKey, DerEncoder {
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 class X509Key implements PublicKey, DerEncoder {
* @exception InvalidKeyException on invalid key encodings.
*/
protected void parseKeyBits() throws InvalidKeyException {
encode();
getEncodedInternal();
}
/*
@ -243,7 +243,7 @@ public class X509Key implements PublicKey, DerEncoder {
/**
* 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 class X509Key implements PublicKey, DerEncoder {
return getEncodedInternal().clone();
}
public byte[] getEncodedInternal() {
private byte[] getEncodedInternal() {
byte[] encoded = encodedKey;
if (encoded == null) {
DerOutputStream out = new DerOutputStream();
@ -314,7 +314,7 @@ public class X509Key implements PublicKey, DerEncoder {
* @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");

View File

@ -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

View File

@ -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

View File

@ -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<Entry> list, String varname) {
for (Entry entry : list) {
if (entry.name.compareToIgnoreCase(varname) == 0) {
return entry;
}
}
return null;
}
static List<Entry> passList = new ArrayList<>();
static List<Entry> entryList = new ArrayList<>();
static List<Entry> pubList = new ArrayList<>();
static List<Entry> privList = new ArrayList<>();
static List<Entry> oasList = new ArrayList<>();
static List<Entry> certList = new ArrayList<>();
static List<Entry> encryptedList = new ArrayList<>();
static List<Entry> failureEntryList = new ArrayList<>();
static {
pubList.add(rsapub);
pubList.add(rsapubbc);
pubList.add(ecsecp256pub.makeCR("ecsecp256pub-r"));
pubList.add(ecsecp256pub.makeCRLF("ecsecp256pub-rn"));
privList.add(rsapriv);
privList.add(rsaprivbc);
privList.add(ecsecp256);
privList.add(ecsecp384);
privList.add(ec25519priv);
privList.add(ed25519ekpi); // The non-EKPI version needs decryption
privList.add(rsaOpenSSL);
oasList.add(oasrfc8410);
oasList.add(oasbcpem);
certList.add(rsaCert);
certList.add(ecCert);
entryList.addAll(pubList);
entryList.addAll(privList);
entryList.addAll(oasList);
entryList.addAll(certList);
encryptedList.add(ed25519ep8);
passList.addAll(entryList);
passList.addAll(encryptedList);
failureEntryList.add(new Entry("emptyPEM", "", DEREncodable.class, null));
failureEntryList.add(new Entry("nullPEM", null, DEREncodable.class, null));
}
static void checkResults(PEMData.Entry entry, String result) {
try {
checkResults(entry.pem(), result);
} catch (AssertionError e) {
throw new AssertionError("Encoder PEM mismatch " +
entry.name(), e);
}
}
static void checkResults(String expected, String result) {
// The below matches the \r\n generated PEM with the PEM passed
// into the test.
String pem = LF.matcher(CR.matcher(expected).replaceAll("")).
replaceAll("");
result = LF.matcher(CR.matcher(result).replaceAll("")).
replaceAll("");
try {
if (pem.compareTo(result) != 0) {
System.out.println("expected:\n" + pem);
System.out.println("generated:\n" + result);
indexDiff(pem, result);
}
} catch (AssertionError e) {
throw new AssertionError("Encoder PEM mismatch ");
}
}
static void indexDiff(String a, String b) {
String lenerr = "";
int len = a.length();
int lenb = b.length();
if (len != lenb) {
lenerr = ": Length mismatch: " + len + " vs " + lenb;
len = Math.min(len, lenb);
}
for (int i = 0; i < len; i++) {
if (a.charAt(i) != b.charAt(i)) {
throw new AssertionError("Char mistmatch, index #" + i +
" (" + a.charAt(i) + " vs " + b.charAt(i) + ")" + lenerr);
}
}
}
}

View File

@ -0,0 +1,510 @@
/*
* 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.
*/
/*
* @test
* @bug 8298420
* @modules java.base/sun.security.pkcs
* java.base/sun.security.util
* @summary Testing basic PEM API decoding
* @enablePreview
*/
import javax.crypto.EncryptedPrivateKeyInfo;
import java.io.*;
import java.lang.Class;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.cert.X509CRL;
import java.security.cert.X509Certificate;
import java.security.interfaces.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.*;
import java.util.Arrays;
import sun.security.util.Pem;
public class PEMDecoderTest {
static HexFormat hex = HexFormat.of();
public static void main(String[] args) throws IOException {
System.out.println("Decoder test:");
PEMData.entryList.forEach(PEMDecoderTest::test);
System.out.println("Decoder test returning DEREncodable class:");
PEMData.entryList.forEach(entry -> test(entry, DEREncodable.class));
System.out.println("Decoder test with encrypted PEM:");
PEMData.encryptedList.forEach(PEMDecoderTest::testEncrypted);
System.out.println("Decoder test with OAS:");
testTwoKeys();
System.out.println("Decoder test RSA PEM setting RSAKey.class returned:");
test(PEMData.rsapriv, RSAKey.class);
System.out.println("Decoder test failures:");
PEMData.failureEntryList.forEach(PEMDecoderTest::testFailure);
System.out.println("Decoder test ecsecp256 PEM asking for ECPublicKey.class returned:");
testFailure(PEMData.ecsecp256, ECPublicKey.class);
System.out.println("Decoder test rsapriv PEM setting P8EKS.class returned:");
testClass(PEMData.rsapriv, RSAPrivateKey.class);
System.out.println("Decoder test rsaOpenSSL P1 PEM asking for RSAPublicKey.class returned:");
testFailure(PEMData.rsaOpenSSL, RSAPublicKey.class);
System.out.println("Decoder test rsapub PEM asking X509EKS.class returned:");
testClass(PEMData.rsapub, X509EncodedKeySpec.class, true);
System.out.println("Decoder test rsapriv PEM asking X509EKS.class returned:");
testClass(PEMData.rsapriv, X509EncodedKeySpec.class, false);
System.out.println("Decoder test RSAcert PEM asking X509EKS.class returned:");
testClass(PEMData.rsaCert, X509EncodedKeySpec.class, false);
System.out.println("Decoder test OAS RFC PEM asking PrivateKey.class returned:");
testClass(PEMData.oasrfc8410, PrivateKey.class, true);
testClass(PEMData.oasrfc8410, PublicKey.class, true);
System.out.println("Decoder test ecsecp256:");
testFailure(PEMData.ecsecp256pub.makeNoCRLF("pubecpem-no"));
System.out.println("Decoder test RSAcert with decryption Decoder:");
PEMDecoder d = PEMDecoder.of().withDecryption("123".toCharArray());
d.decode(PEMData.rsaCert.pem());
System.out.println("Decoder test ecsecp256 private key with decryption Decoder:");
((KeyPair) d.decode(PEMData.ecsecp256.pem())).getPrivate();
System.out.println("Decoder test ecsecp256 to P8EKS:");
d.decode(PEMData.ecsecp256.pem(), PKCS8EncodedKeySpec.class);
System.out.println("Checking if decode() returns the same encoding:");
PEMData.privList.forEach(PEMDecoderTest::testDERCheck);
PEMData.oasList.forEach(PEMDecoderTest::testDERCheck);
System.out.println("Check a Signature/Verify op is successful:");
PEMData.privList.forEach(PEMDecoderTest::testSignature);
PEMData.oasList.forEach(PEMDecoderTest::testSignature);
System.out.println("Checking if ecCSR:");
test(PEMData.ecCSR);
System.out.println("Checking if ecCSR with preData:");
DEREncodable result = PEMDecoder.of().decode(PEMData.ecCSRWithData.pem(), PEMRecord.class);
if (result instanceof PEMRecord rec) {
if (PEMData.preData.compareTo(new String(rec.leadingData())) != 0) {
System.err.println("expected: " + PEMData.preData);
System.err.println("received: " + new String(rec.leadingData()));
throw new AssertionError("ecCSRWithData preData wrong");
}
if (rec.pem().lastIndexOf("F") > rec.pem().length() - 5) {
System.err.println("received: " + rec.pem());
throw new AssertionError("ecCSRWithData: " +
"End of PEM data has an unexpected character");
}
} else {
throw new AssertionError("ecCSRWithData didn't return a PEMRecord");
}
System.out.println("Decoding RSA pub using class PEMRecord:");
result = PEMDecoder.of().decode(PEMData.rsapub.pem(), PEMRecord.class);
if (!(result instanceof PEMRecord)) {
throw new AssertionError("pubecpem didn't return a PEMRecord");
}
if (((PEMRecord) result).type().compareTo(Pem.PUBLIC_KEY) != 0) {
throw new AssertionError("pubecpem PEMRecord didn't decode as a Public Key");
}
testInputStream();
testPEMRecord(PEMData.rsapub);
testPEMRecord(PEMData.ecCert);
testPEMRecord(PEMData.ec25519priv);
testPEMRecord(PEMData.ecCSR);
testPEMRecord(PEMData.ecCSRWithData);
testPEMRecordDecode(PEMData.rsapub);
testPEMRecordDecode(PEMData.ecCert);
testPEMRecordDecode(PEMData.ec25519priv);
testPEMRecordDecode(PEMData.ecCSR);
testPEMRecordDecode(PEMData.ecCSRWithData);
d = PEMDecoder.of();
System.out.println("Check leadingData is null with back-to-back PEMs: ");
String s = new PEMRecord("ONE", "1212").toString()
+ new PEMRecord("TWO", "3434").toString();
var ins = new ByteArrayInputStream(s.getBytes(StandardCharsets.UTF_8));
if (d.decode(ins, PEMRecord.class).leadingData() != null) {
throw new AssertionError("leading data not null on first pem");
}
if (d.decode(ins, PEMRecord.class).leadingData() != null) {
throw new AssertionError("leading data not null on second pem");
}
System.out.println("PASS");
System.out.println("Decode to EncryptedPrivateKeyInfo: ");
EncryptedPrivateKeyInfo ekpi =
d.decode(PEMData.ed25519ep8.pem(), EncryptedPrivateKeyInfo.class);
PrivateKey privateKey;
try {
privateKey = ekpi.getKey(PEMData.ed25519ep8.password());
System.out.println("PASS");
} catch (GeneralSecurityException e) {
throw new AssertionError("ed25519ep8 error", e);
}
// PBE
System.out.println("EncryptedPrivateKeyInfo.encryptKey with PBE: ");
ekpi = EncryptedPrivateKeyInfo.encryptKey(privateKey,
"password".toCharArray(),"PBEWithMD5AndDES", null, null);
try {
ekpi.getKey("password".toCharArray());
System.out.println("PASS");
} catch (Exception e) {
throw new AssertionError("error getting key", e);
}
// PBES2
System.out.println("EncryptedPrivateKeyInfo.encryptKey with default: ");
ekpi = EncryptedPrivateKeyInfo.encryptKey(privateKey
, "password".toCharArray());
try {
ekpi.getKey("password".toCharArray());
System.out.println("PASS");
} catch (Exception e) {
throw new AssertionError("error getting key", e);
}
}
static void testInputStream() throws IOException {
ByteArrayOutputStream ba = new ByteArrayOutputStream(2048);
OutputStreamWriter os = new OutputStreamWriter(ba);
os.write(PEMData.preData);
os.write(PEMData.rsapub.pem());
os.write(PEMData.preData);
os.write(PEMData.rsapub.pem());
os.write(PEMData.postData);
os.flush();
ByteArrayInputStream is = new ByteArrayInputStream(ba.toByteArray());
System.out.println("Decoding 2 RSA pub with pre & post data:");
PEMRecord obj;
int keys = 0;
while (keys++ < 2) {
obj = PEMDecoder.of().decode(is, PEMRecord.class);
if (!PEMData.preData.equalsIgnoreCase(
new String(obj.leadingData()))) {
System.out.println("expected: \"" + PEMData.preData + "\"");
System.out.println("returned: \"" +
new String(obj.leadingData()) + "\"");
throw new AssertionError("Leading data incorrect");
}
System.out.println(" Read public key.");
}
try {
PEMDecoder.of().decode(is, PEMRecord.class);
throw new AssertionError("3rd entry returned a PEMRecord");
} catch (EOFException e) {
System.out.println("Success: No 3rd entry found. EOFE thrown.");
}
// End of stream
try {
System.out.println("Failed: There should be no PEMRecord: " +
PEMDecoder.of().decode(is, PEMRecord.class));
} catch (EOFException e) {
System.out.println("Success");
return;
} catch (Exception e) {
throw new AssertionError("Caught unexpected exception " +
"should have been IOE EOF.");
}
throw new AssertionError("Failed");
}
static void testPEMRecord(PEMData.Entry entry) {
PEMRecord r = PEMDecoder.of().decode(entry.pem(), PEMRecord.class);
String expected = entry.pem().split("-----")[2].replace(System.lineSeparator(), "");
try {
PEMData.checkResults(expected, r.pem());
} catch (AssertionError e) {
System.err.println("expected:\n" + expected);
System.err.println("received:\n" + r.pem());
throw e;
}
boolean result = switch(r.type()) {
case Pem.PRIVATE_KEY ->
PrivateKey.class.isAssignableFrom(entry.clazz());
case Pem.PUBLIC_KEY ->
PublicKey.class.isAssignableFrom(entry.clazz());
case Pem.CERTIFICATE, Pem.X509_CERTIFICATE ->
entry.clazz().isAssignableFrom(X509Certificate.class);
case Pem.X509_CRL ->
entry.clazz().isAssignableFrom(X509CRL.class);
case "CERTIFICATE REQUEST" ->
entry.clazz().isAssignableFrom(PEMRecord.class);
default -> false;
};
if (!result) {
System.err.println("PEMRecord type is a " + r.type());
System.err.println("Entry is a " + entry.clazz().getName());
throw new AssertionError("PEMRecord class didn't match:" +
entry.name());
}
System.out.println("Success (" + entry.name() + ")");
}
static void testPEMRecordDecode(PEMData.Entry entry) {
PEMRecord r = PEMDecoder.of().decode(entry.pem(), PEMRecord.class);
DEREncodable de = PEMDecoder.of().decode(r.toString());
boolean result = switch(r.type()) {
case Pem.PRIVATE_KEY ->
PrivateKey.class.isAssignableFrom(de.getClass());
case Pem.PUBLIC_KEY ->
PublicKey.class.isAssignableFrom(de.getClass());
case Pem.CERTIFICATE, Pem.X509_CERTIFICATE ->
(de instanceof X509Certificate);
case Pem.X509_CRL -> (de instanceof X509CRL);
case "CERTIFICATE REQUEST" -> (de instanceof PEMRecord);
default -> false;
};
if (!result) {
System.err.println("Entry is a " + entry.clazz().getName());
System.err.println("PEMRecord type is a " + r.type());
System.err.println("Returned was a " + entry.clazz().getName());
throw new AssertionError("PEMRecord class didn't match:" +
entry.name());
}
System.out.println("Success (" + entry.name() + ")");
}
static void testFailure(PEMData.Entry entry) {
testFailure(entry, entry.clazz());
}
static void testFailure(PEMData.Entry entry, Class c) {
try {
test(entry.pem(), c, PEMDecoder.of());
if (entry.pem().indexOf('\r') != -1) {
System.out.println("Found a CR.");
}
if (entry.pem().indexOf('\n') != -1) {
System.out.println("Found a LF");
}
throw new AssertionError("Failure with " +
entry.name() + ": Not supposed to succeed.");
} catch (NullPointerException e) {
System.out.println("PASS (" + entry.name() + "): " + e.getClass() +
": " + e.getMessage());
} catch (IOException | RuntimeException e) {
System.out.println("PASS (" + entry.name() + "): " + e.getClass() +
": " + e.getMessage());
}
}
static DEREncodable testEncrypted(PEMData.Entry entry) {
PEMDecoder decoder = PEMDecoder.of();
if (!Objects.equals(entry.clazz(), EncryptedPrivateKeyInfo.class)) {
decoder = decoder.withDecryption(entry.password());
}
try {
return test(entry.pem(), entry.clazz(), decoder);
} catch (Exception | AssertionError e) {
throw new RuntimeException("Error with PEM (" + entry.name() +
"): " + e.getMessage(), e);
}
}
// Change the Entry to use the given class as the expected class returned
static DEREncodable test(PEMData.Entry entry, Class c) {
return test(entry.newClass(c));
}
// Run test with a given Entry
static DEREncodable test(PEMData.Entry entry) {
try {
DEREncodable r = test(entry.pem(), entry.clazz(), PEMDecoder.of());
System.out.println("PASS (" + entry.name() + ")");
return r;
} catch (Exception | AssertionError e) {
throw new RuntimeException("Error with PEM (" + entry.name() +
"): " + e.getMessage(), e);
}
}
static List getInterfaceList(Class ccc) {
Class<?>[] interfaces = ccc.getInterfaces();
List<Class> list = new ArrayList<>(Arrays.asList(interfaces));
var x = ccc.getSuperclass();
if (x != null) {
list.add(x);
}
List<Class> results = new ArrayList<>(list);
if (list.size() > 0) {
for (Class cname : list) {
try {
if (cname != null &&
cname.getName().startsWith("java.security.")) {
results.addAll(getInterfaceList(cname));
}
} catch (Exception e) {
System.err.println("Exception with " + cname);
}
}
}
return results;
}
/**
* Perform the decoding test with the given decoder, on the given pem, and
* expect the clazz to be returned.
*/
static DEREncodable test(String pem, Class clazz, PEMDecoder decoder)
throws IOException {
DEREncodable pk = decoder.decode(pem);
// Check that clazz matches what pk returned.
if (pk.getClass().equals(clazz)) {
return pk;
}
// Search interfaces and inheritance to find a match with clazz
List<Class> list = getInterfaceList(pk.getClass());
for (Class cc : list) {
if (cc != null && cc.equals(clazz)) {
return pk;
}
}
throw new RuntimeException("Entry did not contain expected: " +
clazz.getName());
}
// Run the same key twice through the same decoder and make sure the
// result is the same
static void testTwoKeys() throws IOException {
PublicKey p1, p2;
PEMDecoder pd = PEMDecoder.of();
p1 = pd.decode(PEMData.rsapub.pem(), RSAPublicKey.class);
p2 = pd.decode(PEMData.rsapub.pem(), RSAPublicKey.class);
if (!Arrays.equals(p1.getEncoded(), p2.getEncoded())) {
System.err.println("These two should have matched:");
System.err.println(hex.parseHex(new String(p1.getEncoded())));
System.err.println(hex.parseHex(new String(p2.getEncoded())));
throw new AssertionError("Two decoding of the same" +
" key failed to match: ");
}
}
static void testClass(PEMData.Entry entry, Class clazz) throws IOException {
var pk = PEMDecoder.of().decode(entry.pem(), clazz);
}
static void testClass(PEMData.Entry entry, Class clazz, boolean pass)
throws RuntimeException {
try {
testClass(entry, clazz);
} catch (Exception e) {
if (pass) {
throw new RuntimeException(e);
}
}
}
// Run test with a given Entry
static void testDERCheck(PEMData.Entry entry) {
if (entry.name().equals("rsaOpenSSL") || // PKCS1 data
entry.name().equals("ed25519ekpi")) {
return;
}
PKCS8EncodedKeySpec p8 = PEMDecoder.of().decode(entry.pem(),
PKCS8EncodedKeySpec.class);
int result = Arrays.compare(entry.der(), p8.getEncoded());
if (result != 0) {
System.err.println("Compare error with " + entry.name() + "(" +
result + ")");
System.err.println("Expected DER: " + HexFormat.of().
formatHex(entry.der()));
System.err.println("Returned DER: " + HexFormat.of().
formatHex(p8.getEncoded()));
throw new AssertionError("Failed to match " +
"expected DER");
}
System.out.println("PASS (" + entry.name() + ")");
System.out.flush();
}
/**
* Run decoded keys through Signature to make sure they are valid keys
*/
static void testSignature(PEMData.Entry entry) {
Signature s;
byte[] data = "12345678".getBytes();
PrivateKey privateKey;
DEREncodable d = PEMDecoder.of().decode(entry.pem());
switch (d) {
case PrivateKey p -> privateKey = p;
case KeyPair kp -> privateKey = kp.getPrivate();
case EncryptedPrivateKeyInfo e -> {
System.out.println("SKIP: EncryptedPrivateKeyInfo " +
entry.name());
return;
}
default -> throw new AssertionError("Private key " +
"should not be null");
}
String algorithm = switch(privateKey.getAlgorithm()) {
case "EC" -> "SHA256withECDSA";
case "EdDSA" -> "EdDSA";
case null -> {
System.out.println("Algorithm is null " +
entry.name());
throw new AssertionError("PrivateKey algorithm" +
"should not be null");
}
default -> "SHA256with" + privateKey.getAlgorithm();
};
try {
if (d instanceof PrivateKey) {
s = Signature.getInstance(algorithm);
s.initSign(privateKey);
s.update(data);
s.sign();
System.out.println("PASS (Sign): " + entry.name());
} else if (d instanceof KeyPair) {
s = Signature.getInstance(algorithm);
s.initSign(privateKey);
s.update(data);
byte[] sig = s.sign();
s.initVerify(((KeyPair)d).getPublic());
s.verify(sig);
System.out.println("PASS (Sign/Verify): " + entry.name());
} else {
System.out.println("SKIP: " + entry.name());
}
} catch (Exception e) {
System.out.println("FAIL: " + entry.name());
throw new AssertionError(e);
}
}
}

View File

@ -0,0 +1,199 @@
/*
* 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.
*/
/*
* @test
* @bug 8298420
* @summary Testing basic PEM API encoding
* @enablePreview
* @modules java.base/sun.security.util
*/
import sun.security.util.Pem;
import javax.crypto.EncryptedPrivateKeyInfo;
import javax.crypto.spec.PBEParameterSpec;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.InvalidParameterSpecException;
import java.util.*;
public class PEMEncoderTest {
static Map<String, DEREncodable> keymap;
public static void main(String[] args) throws Exception {
PEMEncoder encoder = PEMEncoder.of();
// These entries are removed
var newEntryList = new ArrayList<>(PEMData.entryList);
newEntryList.remove(PEMData.getEntry("rsaOpenSSL"));
newEntryList.remove(PEMData.getEntry("ecsecp256"));
newEntryList.remove(PEMData.getEntry("ecsecp384"));
keymap = generateObjKeyMap(newEntryList);
System.out.println("Same instance re-encode test:");
keymap.keySet().stream().forEach(key -> test(key, encoder));
System.out.println("New instance re-encode test:");
keymap.keySet().stream().forEach(key -> test(key, PEMEncoder.of()));
System.out.println("Same instance re-encode testToString:");
keymap.keySet().stream().forEach(key -> testToString(key, encoder));
System.out.println("New instance re-encode testToString:");
keymap.keySet().stream().forEach(key -> testToString(key,
PEMEncoder.of()));
keymap = generateObjKeyMap(PEMData.encryptedList);
System.out.println("Same instance Encoder match test:");
keymap.keySet().stream().forEach(key -> testEncryptedMatch(key, encoder));
System.out.println("Same instance Encoder new withEnc test:");
keymap.keySet().stream().forEach(key -> testEncrypted(key, encoder));
System.out.println("New instance Encoder and withEnc test:");
keymap.keySet().stream().forEach(key -> testEncrypted(key, PEMEncoder.of()));
System.out.println("Same instance encrypted Encoder test:");
PEMEncoder encEncoder = encoder.withEncryption("fish".toCharArray());
keymap.keySet().stream().forEach(key -> testSameEncryptor(key, encEncoder));
try {
encoder.withEncryption(null);
} catch (Exception e) {
if (!(e instanceof NullPointerException)) {
throw new Exception("Should have been a NullPointerException thrown");
}
}
PEMDecoder d = PEMDecoder.of();
PEMRecord pemRecord =
d.decode(PEMData.ed25519ep8.pem(), PEMRecord.class);
PEMData.checkResults(PEMData.ed25519ep8, pemRecord.toString());
}
static Map generateObjKeyMap(List<PEMData.Entry> list) {
Map<String, DEREncodable> keymap = new HashMap<>();
PEMDecoder pemd = PEMDecoder.of();
for (PEMData.Entry entry : list) {
try {
if (entry.password() != null) {
keymap.put(entry.name(), pemd.withDecryption(
entry.password()).decode(entry.pem()));
} else {
keymap.put(entry.name(), pemd.decode(entry.pem(),
entry.clazz()));
}
} catch (Exception e) {
System.err.println("Verify PEMDecoderTest passes before " +
"debugging this test.");
throw new AssertionError("Failed to initialize map on" +
" entry \"" + entry.name() + "\"", e);
}
}
return keymap;
}
static void test(String key, PEMEncoder encoder) {
byte[] result;
PEMData.Entry entry = PEMData.getEntry(key);
try {
result = encoder.encode(keymap.get(key));
} catch (RuntimeException e) {
throw new AssertionError("Encoder use failure with " +
entry.name(), e);
}
PEMData.checkResults(entry, new String(result, StandardCharsets.UTF_8));
System.out.println("PASS: " + entry.name());
}
static void testToString(String key, PEMEncoder encoder) {
String result;
PEMData.Entry entry = PEMData.getEntry(key);
try {
result = encoder.encodeToString(keymap.get(key));
} catch (RuntimeException e) {
throw new AssertionError("Encoder use failure with " +
entry.name(), e);
}
PEMData.checkResults(entry, result);
System.out.println("PASS: " + entry.name());
}
/*
Test cannot verify PEM was the same as known PEM because we have no
public access to the AlgoritmID.params and PBES2Parameters.
*/
static void testEncrypted(String key, PEMEncoder encoder) {
PEMData.Entry entry = PEMData.getEntry(key);
try {
encoder.withEncryption(
(entry.password() != null ? entry.password() :
"fish".toCharArray()))
.encodeToString(keymap.get(key));
} catch (RuntimeException e) {
throw new AssertionError("Encrypted encoder failed with " +
entry.name(), e);
}
System.out.println("PASS: " + entry.name());
}
/*
Test cannot verify PEM was the same as known PEM because we have no
public access to the AlgoritmID.params and PBES2Parameters.
*/
static void testSameEncryptor(String key, PEMEncoder encoder) {
PEMData.Entry entry = PEMData.getEntry(key);
try {
encoder.encodeToString(keymap.get(key));
} catch (RuntimeException e) {
throw new AssertionError("Encrypted encoder failured with " +
entry.name(), e);
}
System.out.println("PASS: " + entry.name());
}
static void testEncryptedMatch(String key, PEMEncoder encoder) {
String result;
PEMData.Entry entry = PEMData.getEntry(key);
try {
PrivateKey pkey = (PrivateKey) keymap.get(key);
EncryptedPrivateKeyInfo ekpi = PEMDecoder.of().decode(entry.pem(),
EncryptedPrivateKeyInfo.class);
if (entry.password() != null) {
EncryptedPrivateKeyInfo.encryptKey(pkey, entry.password(),
Pem.DEFAULT_ALGO, ekpi.getAlgParameters().
getParameterSpec(PBEParameterSpec.class),
null);
}
result = encoder.encodeToString(ekpi);
} catch (RuntimeException | InvalidParameterSpecException e) {
throw new AssertionError("Encrypted encoder failure with " +
entry.name(), e);
}
PEMData.checkResults(entry, result);
System.out.println("PASS: " + entry.name());
}
}

View File

@ -0,0 +1,82 @@
/*
* 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.
*/
/**
* @test
* @bug 8298420
* @summary Testing encryptKey
* @enablePreview
*/
import javax.crypto.EncryptedPrivateKeyInfo;
import javax.crypto.SecretKey;
import javax.crypto.spec.PBEParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.AlgorithmParameters;
import java.security.PEMDecoder;
import java.security.PrivateKey;
import java.util.Arrays;
public class EncryptKey {
private static final String encEdECKey =
"""
-----BEGIN ENCRYPTED PRIVATE KEY-----
MIGqMGYGCSqGSIb3DQEFDTBZMDgGCSqGSIb3DQEFDDArBBRyYnoNyrcqvubzch00
jyuAb5YizgICEAACARAwDAYIKoZIhvcNAgkFADAdBglghkgBZQMEAQIEEM8BgEgO
vdMyi46+Dw7cOjwEQLtx5ME0NOOo7vlCGm3H/4j+Tf5UXrMb1UrkPjqc8OiLbC0n
IycFtI70ciPjgwDSjtCcPxR8fSxJPrm2yOJsRVo=
-----END ENCRYPTED PRIVATE KEY-----
""";
private static final String passwdText = "fish";
private static final char[] password = passwdText.toCharArray();
private static final SecretKey key = new SecretKeySpec(
passwdText.getBytes(), "PBE");
public static void main(String[] args) throws Exception {
EncryptedPrivateKeyInfo ekpi = PEMDecoder.of().decode(encEdECKey,
EncryptedPrivateKeyInfo.class);
PrivateKey priKey = PEMDecoder.of().withDecryption(password).
decode(encEdECKey, PrivateKey.class);
AlgorithmParameters ap = ekpi.getAlgParameters();
// Test encryptKey(PrivateKey, char[], String, ... )
var e = EncryptedPrivateKeyInfo.encryptKey(priKey, password,
ekpi.getAlgName(), ap.getParameterSpec(PBEParameterSpec.class),
null);
if (!Arrays.equals(ekpi.getEncryptedData(), e.getEncryptedData())) {
throw new AssertionError("encryptKey() didn't match" +
" with expected.");
}
// Test encryptKey(PrivateKey, Key, String, ...)
e = EncryptedPrivateKeyInfo.encryptKey(priKey, key, ekpi.getAlgName(),
ap.getParameterSpec(PBEParameterSpec.class),null, null);
if (!Arrays.equals(ekpi.getEncryptedData(), e.getEncryptedData())) {
throw new AssertionError("encryptKey() didn't match" +
" with expected.");
}
}
}

View File

@ -0,0 +1,76 @@
/*
* 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.
*/
/**
* @test
* @bug 8298420
* @summary Testing getKey
* @enablePreview
*/
import javax.crypto.EncryptedPrivateKeyInfo;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.security.PEMDecoder;
import java.security.PrivateKey;
import java.util.Arrays;
public class GetKey {
private static final String encEdECKey =
"""
-----BEGIN ENCRYPTED PRIVATE KEY-----
MIGqMGYGCSqGSIb3DQEFDTBZMDgGCSqGSIb3DQEFDDArBBRyYnoNyrcqvubzch00
jyuAb5YizgICEAACARAwDAYIKoZIhvcNAgkFADAdBglghkgBZQMEAQIEEM8BgEgO
vdMyi46+Dw7cOjwEQLtx5ME0NOOo7vlCGm3H/4j+Tf5UXrMb1UrkPjqc8OiLbC0n
IycFtI70ciPjgwDSjtCcPxR8fSxJPrm2yOJsRVo=
-----END ENCRYPTED PRIVATE KEY-----
""";
private static final String passwdText = "fish";
private static final char[] password = passwdText.toCharArray();
private static final SecretKey key = new SecretKeySpec(
passwdText.getBytes(), "PBE");
public static void main(String[] args) throws Exception {
EncryptedPrivateKeyInfo ekpi = PEMDecoder.of().decode(encEdECKey,
EncryptedPrivateKeyInfo.class);
PrivateKey priKey = PEMDecoder.of().withDecryption(password).
decode(encEdECKey, PrivateKey.class);
// Test getKey(password)
if (!Arrays.equals(priKey.getEncoded(),
ekpi.getKey(password).getEncoded())) {
throw new AssertionError("getKey(char[]) didn't "
+ "match with expected.");
}
// Test getKey(key, provider)
if (!Arrays.equals(priKey.getEncoded(),
ekpi.getKey(key, null).getEncoded())) {
throw new AssertionError("getKey(key, provider) " +
"didn't match with expected.");
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2015, 2021, 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
@ -30,22 +30,18 @@
* java.base/sun.security.util
* java.base/sun.security.provider
* java.base/sun.security.x509
* @compile -XDignore.symbol.file PKCS8Test.java
* @run testng PKCS8Test
* @run main PKCS8Test
*/
import java.io.IOException;
import java.math.BigInteger;
import java.security.InvalidKeyException;
import java.util.Arrays;
import java.util.HexFormat;
import jdk.test.lib.hexdump.ASN1Formatter;
import jdk.test.lib.hexdump.HexPrinter;
import org.testng.Assert;
import org.testng.annotations.Test;
import sun.security.pkcs.PKCS8Key;
import sun.security.provider.DSAPrivateKey;
import sun.security.util.DerValue;
public class PKCS8Test {
@ -62,8 +58,7 @@ public class PKCS8Test {
"3009020102020103020104" + // p=2, q=3, g=4
"0403020101"); // PrivateKey OCTET int x = 1
@Test
public void test() throws IOException {
public static void main(String[] args) throws Exception {
byte[] encodedKey = new DSAPrivateKey(
BigInteger.valueOf(1),
@ -71,34 +66,45 @@ public class PKCS8Test {
BigInteger.valueOf(3),
BigInteger.valueOf(4)).getEncoded();
Assert.assertTrue(Arrays.equals(encodedKey, EXPECTED),
if (!Arrays.equals(encodedKey, EXPECTED)) {
throw new AssertionError(
HexPrinter.simple()
.formatter(ASN1Formatter.formatter())
.toString(encodedKey));
.formatter(ASN1Formatter.formatter())
.toString(encodedKey));
}
PKCS8Key decodedKey = (PKCS8Key)PKCS8Key.parseKey(encodedKey);
Assert.assertEquals(decodedKey.getAlgorithm(), ALGORITHM);
Assert.assertEquals(decodedKey.getFormat(), FORMAT);
Assert.assertEquals(decodedKey.getAlgorithmId().toString(),
EXPECTED_ALG_ID_CHRS);
assert(ALGORITHM.equalsIgnoreCase(decodedKey.getAlgorithm()));
assert(FORMAT.equalsIgnoreCase(decodedKey.getFormat()));
assert(EXPECTED_ALG_ID_CHRS.equalsIgnoreCase(decodedKey.getAlgorithmId().toString()));
byte[] encodedOutput = decodedKey.getEncoded();
Assert.assertTrue(Arrays.equals(encodedOutput, EXPECTED),
if (!Arrays.equals(encodedOutput, EXPECTED)) {
throw new AssertionError(
HexPrinter.simple()
.formatter(ASN1Formatter.formatter())
.toString(encodedOutput));
.formatter(ASN1Formatter.formatter())
.toString(encodedOutput));
}
// Test additional fields
enlarge(0, "8000"); // attributes
enlarge(1, "810100"); // public key for v2
enlarge(1, "8000", "810100"); // both
Assert.assertThrows(() -> enlarge(2)); // bad ver
Assert.assertThrows(() -> enlarge(0, "8000", "8000")); // no dup
Assert.assertThrows(() -> enlarge(0, "810100")); // no public in v1
Assert.assertThrows(() -> enlarge(1, "810100", "8000")); // bad order
Assert.assertThrows(() -> enlarge(1, "820100")); // bad tag
// PKCSv2 testing done by PEMEncoder/PEMDecoder tests
assertThrows(() -> enlarge(2));
assertThrows(() -> enlarge(0, "8000", "8000")); // no dup
assertThrows(() -> enlarge(0, "810100")); // no public in v1
assertThrows(() -> enlarge(1, "810100", "8000")); // bad order
assertThrows(() -> enlarge(1, "820100")); // bad tag
}
private static void assertThrows(Runnable o) {
try {
o.run();
throw new AssertionError("Test failed");
} catch (Exception e) {}
}
/**
@ -107,7 +113,7 @@ public class PKCS8Test {
* @param newVersion new version
* @param fields extra fields to add, in hex
*/
static void enlarge(int newVersion, String... fields) throws IOException {
static void enlarge(int newVersion, String... fields) {
byte[] original = EXPECTED.clone();
int length = original.length;
for (String field : fields) { // append fields
@ -116,9 +122,13 @@ public class PKCS8Test {
System.arraycopy(add, 0, original, length, add.length);
length += add.length;
}
Assert.assertTrue(length < 127);
original[1] = (byte)(length - 2); // the length field inside DER
original[4] = (byte)newVersion; // the version inside DER
PKCS8Key.parseKey(original);
assert (length < 127);
original[1] = (byte) (length - 2); // the length field inside DER
original[4] = (byte) newVersion; // the version inside DER
try {
PKCS8Key.parseKey(original);
} catch (InvalidKeyException e) {
throw new RuntimeException(e);
}
}
}