diff --git a/src/java.base/share/classes/java/security/AsymmetricKey.java b/src/java.base/share/classes/java/security/AsymmetricKey.java index d37afe9bfea..2177332d174 100644 --- a/src/java.base/share/classes/java/security/AsymmetricKey.java +++ b/src/java.base/share/classes/java/security/AsymmetricKey.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2026, 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 non-sealed interface AsymmetricKey extends Key, DEREncodable { +public non-sealed interface AsymmetricKey extends Key, BinaryEncodable { /** * Returns the parameters associated with this key. * The parameters are optional and may be either diff --git a/src/java.base/share/classes/java/security/DEREncodable.java b/src/java.base/share/classes/java/security/BinaryEncodable.java similarity index 79% rename from src/java.base/share/classes/java/security/DEREncodable.java rename to src/java.base/share/classes/java/security/BinaryEncodable.java index 1401336037c..bd5d05ee4ec 100644 --- a/src/java.base/share/classes/java/security/DEREncodable.java +++ b/src/java.base/share/classes/java/security/BinaryEncodable.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2026, 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,20 +25,22 @@ 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; +import jdk.internal.javac.PreviewFeature; + /** * 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. + * binary-encodable cryptographic material. + * + *
This sealed interface may evolve. When using {@code switch}, always include a + * {@code default} case rather than relying on the classes specified in the + * {@code permits} clause to remain fixed. An exhaustive {@code switch} may + * result in a {@link MatchException}. * * @see AsymmetricKey * @see KeyPair @@ -49,11 +51,11 @@ import java.security.spec.X509EncodedKeySpec; * @see X509CRL * @see PEM * - * @since 25 + * @since 27 */ @PreviewFeature(feature = PreviewFeature.Feature.PEM_API) -public sealed interface DEREncodable permits AsymmetricKey, KeyPair, +public sealed interface BinaryEncodable permits AsymmetricKey, KeyPair, PKCS8EncodedKeySpec, X509EncodedKeySpec, EncryptedPrivateKeyInfo, X509Certificate, X509CRL, PEM { } diff --git a/src/java.base/share/classes/java/security/KeyPair.java b/src/java.base/share/classes/java/security/KeyPair.java index 39c98501fea..0efd134c93c 100644 --- a/src/java.base/share/classes/java/security/KeyPair.java +++ b/src/java.base/share/classes/java/security/KeyPair.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1996, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1996, 2026, 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 @@ -37,7 +37,7 @@ package java.security; * @since 1.1 */ -public final class KeyPair implements java.io.Serializable, DEREncodable { +public final class KeyPair implements java.io.Serializable, BinaryEncodable { @java.io.Serial private static final long serialVersionUID = -7565189502268009837L; diff --git a/src/java.base/share/classes/java/security/PEM.java b/src/java.base/share/classes/java/security/PEM.java index 2068fb707dc..13ee9f107cf 100644 --- a/src/java.base/share/classes/java/security/PEM.java +++ b/src/java.base/share/classes/java/security/PEM.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 2026, 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,53 +27,43 @@ package java.security; import jdk.internal.javac.PreviewFeature; +import jdk.internal.ref.CleanerFactory; +import sun.security.util.KeyUtil; import sun.security.util.Pem; import java.io.InputStream; +import java.nio.charset.StandardCharsets; import java.util.Base64; import java.util.Objects; /** - * {@code PEM} is a {@link DEREncodable} that represents Privacy-Enhanced - * Mail (PEM) data by its type and Base64-encoded content. + * A {@link BinaryEncodable} representing a Privacy-Enhanced Mail (PEM) structure + * composed of a type identifier, Base64-encoded content, and optional + * leading data that precedes the PEM header. * - *
The {@link PEMDecoder#decode(String)} and - * {@link PEMDecoder#decode(InputStream)} methods return a {@code PEM} object - * when the data type cannot be represented by a cryptographic object. - * If you need access to the leading data of a PEM text, or want to - * handle the text content directly, use the decoding methods - * {@link PEMDecoder#decode(String, Class)} or - * {@link PEMDecoder#decode(InputStream, Class)} with {@code PEM.class} as an - * argument type. - * - *
A {@code PEM} object can be encoded back to its textual format by calling - * {@link #toString()} or by using the encode methods in {@link PEMEncoder}. - * - *
When constructing a {@code PEM} instance, both {@code type} and - * {@code content} must not be {@code null}. - * - *
No validation is performed during instantiation to ensure that - * {@code type} conforms to RFC 7468 or other legacy formats, that - * {@code content} is valid Base64 data, or that {@code content} matches the - * {@code type}. - - *
Common {@code type} values include, but are not limited to: + *
The {@code type} is the label in the PEM header, following the + * {@code BEGIN} keyword and excluding the encapsulation boundaries. + * Common {@code type} values include, but are not limited to: * CERTIFICATE, CERTIFICATE REQUEST, ATTRIBUTE CERTIFICATE, X509 CRL, PKCS7, * CMS, PRIVATE KEY, ENCRYPTED PRIVATE KEY, and PUBLIC KEY. * - *
{@code leadingData} is {@code null} if there is no data preceding the PEM - * header during decoding. {@code leadingData} can be useful for reading - * metadata that accompanies the PEM data. Because the value may represent a large - * amount of data, it is not defensively copied by the constructor, and the - * {@link #leadingData()} method does not return a clone. Modification of the - * passed-in or returned array changes the value stored in this record. + *
Instances of this class are returned by {@link PEMDecoder#decode(String)} + * and {@link PEMDecoder#decode(InputStream)} when the content cannot be represented + * as a cryptographic object. To explicitly retrieve a {@code PEM} instance + * with access to the leading data, use {@link PEMDecoder#decode(String, Class)} + * or {@link PEMDecoder#decode(InputStream, Class)} with {@code PEM.class} as the + * type. * - * @param type the type identifier from the PEM header, without PEM syntax - * labels; for example, for a public key, {@code type} would be - * "PUBLIC KEY" - * @param content the Base64-encoded data, excluding the PEM header and footer - * @param leadingData any non-PEM data that precedes the PEM header during - * decoding. This value may be {@code null}. + *
A {@code PEM} object can be encoded to its textual representation by + * invoking {@link #toString()} or by using {@link PEMEncoder}. + * + *
To construct a {@code PEM} instance, {@code type} and + * {@code base64Content} must be non-{@code null}. For constructors that accept + * {@code leadingData}, it must also be non-{@code null}. + * + *
No validation is performed to ensure that the {@code type} conforms to + * RFC 7468 or legacy formats, or that the content corresponds to the declared + * {@code type}. * * @spec https://www.rfc-editor.org/info/rfc7468 * RFC 7468: Textual Encodings of PKIX, PKCS, and CMS Structures @@ -84,64 +74,168 @@ import java.util.Objects; * @since 26 */ @PreviewFeature(feature = PreviewFeature.Feature.PEM_API) -public record PEM(String type, String content, byte[] leadingData) - implements DEREncodable { +public final class PEM implements BinaryEncodable { + + private final String type; + private final byte[] content; + private byte[] leadingData; /** - * Creates a {@code PEM} instance with the specified parameters. + * Creates a {@code PEM} instance with the specified type, Base64-encoded + * content string, and leading data byte array. * - * @param type the PEM type identifier - * @param content the Base64-encoded data, excluding the PEM header and footer - * @param leadingData any non-PEM data read during the decoding process - * before the PEM header. This value may be {@code null}. - * @throws IllegalArgumentException if {@code type} is incorrectly formatted - * @throws NullPointerException if {@code type} or {@code content} is {@code null} + * @param type the PEM type identifier; must not contain PEM encapsulation + * syntax + * @param base64Content the Base64-encoded content, excluding the PEM header + * and footer + * @param leadingData data that precedes the PEM header. + * This array is defensively copied. + * + * @throws IllegalArgumentException if {@code type} contains PEM + * encapsulation syntax + * @throws NullPointerException if any parameter is {@code null} */ - public PEM { - Objects.requireNonNull(type, "\"type\" cannot be null."); - Objects.requireNonNull(content, "\"content\" cannot be null."); + public PEM(String type, String base64Content, byte[] leadingData) { + Objects.requireNonNull(base64Content, "base64Content cannot be null"); + this(type, base64Content.getBytes(StandardCharsets.ISO_8859_1), + leadingData); + } - // With no validity checking on `type`, the constructor accept anything - // including lowercase. The onus is on the caller. + /** + * Creates a {@code PEM} instance with the specified type and Base64-encoded + * content string. + * + * @param type the PEM type identifier; must not contain PEM encapsulation + * syntax + * @param base64Content the Base64-encoded content, excluding the PEM header + * and footer + * @throws IllegalArgumentException if {@code type} contains PEM + * encapsulation syntax + * @throws NullPointerException if any parameter is {@code null} + */ + public PEM(String type, String base64Content) { + Objects.requireNonNull(base64Content, "base64Content cannot be null"); + this(type, base64Content.getBytes(StandardCharsets.ISO_8859_1)); + } + + /** + * Creates a {@code PEM} instance with the specified type and Base64-encoded + * content and leading data as byte arrays. + * + * @param type the PEM type identifier; must not contain PEM encapsulation + * syntax + * @param base64Content the Base64-encoded content, excluding the PEM header + * and footer. This array is defensively copied. + * @param leadingData data that precedes the PEM header. + * This array is defensively copied. + * + * @throws IllegalArgumentException if {@code type} contains PEM + * encapsulation syntax + * @throws NullPointerException if any parameter is {@code null} + * + * @since 27 + */ + public PEM(String type, byte[] base64Content, byte[] leadingData) { + this(type, base64Content); + this.leadingData = Objects.requireNonNull( + leadingData, "leadingData cannot be null").clone(); + } + + /** + * Creates a {@code PEM} instance with the specified type and Base64-encoded + * content byte array. + * + * @param type the PEM type identifier; must not contain PEM encapsulation + * syntax + * @param base64Content the Base64-encoded content, excluding the PEM header + * and footer. This array is defensively copied. + * @throws IllegalArgumentException if {@code type} contains PEM + * encapsulation syntax + * @throws NullPointerException if any parameter is {@code null} + * + * @since 27 + */ + public PEM(String type, byte[] base64Content) { + Objects.requireNonNull(type, "type cannot be null"); + Objects.requireNonNull(base64Content, "base64Content cannot be null"); + + // The `type` is not checked against any specification. The onus is on + // the caller. Only minor formatting checks are done if (type.startsWith("-") || type.startsWith("BEGIN ") || type.startsWith("END ")) { throw new IllegalArgumentException("PEM syntax labels found. " + "Only the PEM type identifier is allowed."); } + + content = base64Content.clone(); + this.type = type; + final var c = content; + CleanerFactory.cleaner().register(this, () -> KeyUtil.clear(c)); } /** - * Creates a {@code PEM} instance with the specified type and content. This - * constructor sets {@code leadingData} to {@code null}. + * Returns the PEM type identifier. * - * @param type the PEM type identifier - * @param content the Base64-encoded data, excluding the PEM header and footer - * @throws IllegalArgumentException if {@code type} is incorrectly formatted - * @throws NullPointerException if {@code type} or {@code content} is {@code null} + * @return the PEM type identifier */ - public PEM(String type, String content) { - this(type, content, null); + public String type() { + return type; } /** - * Returns the PEM formatted string containing the {@code type} and - * Base64-encoded {@code content}. {@code leadingData} is not included. + * Returns the leading data that preceded the PEM header in the decoded + * input. * - * @return the PEM text representation + * @return a newly-allocated byte array containing leading data, or + * {@code null} if no leading data is present */ - @Override - final public String toString() { - return Pem.pemEncoded(this); + public byte[] leadingData() { + return (leadingData != null) ? leadingData.clone() : null; } /** - * Returns a Base64-decoded byte array of {@code content}, using + * Returns the Base64-encoded content. + * + * @return a newly-allocated byte array containing the Base64 content + * + * @since 27 + */ + public byte[] content() { + return content.clone(); + } + + /** + * Returns the Base64-decoded content as a byte array, using * {@link Base64#getMimeDecoder()}. * - * @return a decoded byte array + * @return a newly-allocated byte array containing the decoded content * @throws IllegalArgumentException if decoding fails */ - final public byte[] decode() { + public byte[] decode() { return Base64.getMimeDecoder().decode(content); } + + /** + * Returns a PEM string representation of this object, using {@code type} + * for the header and footer lines and {@code content} for the Base64 body. + * + * @return the PEM-formatted string + */ + @Override + public String toString() { + return new String(Pem.pemEncoded(type, content), + StandardCharsets.ISO_8859_1); + } + + /* + * Returns the PEM string representation as a byte array. + */ + byte[] toTextualByteArray() { + return Pem.pemEncoded(type, content); + } + + // Clear internal content + void clear() { + KeyUtil.clear(content); + } } diff --git a/src/java.base/share/classes/java/security/PEMDecoder.java b/src/java.base/share/classes/java/security/PEMDecoder.java index 7a4b7753876..f5a6a70d0f5 100644 --- a/src/java.base/share/classes/java/security/PEMDecoder.java +++ b/src/java.base/share/classes/java/security/PEMDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 2026, 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,87 +34,85 @@ import sun.security.util.KeyUtil; import sun.security.util.Pem; import javax.crypto.EncryptedPrivateKeyInfo; +import javax.crypto.CryptoException; import javax.crypto.spec.PBEKeySpec; import java.io.*; import java.lang.ref.Reference; 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 cryptographic * 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-encoded binary encoding enclosed by a type-identifying header + * lists (CRLs). It is defined in RFC 1421 and RFC 7468. PEM consists of + * Base64-encoded content enclosed by a type-identifying header * and footer. * *
The {@link #decode(String)} and {@link #decode(InputStream)} methods * return an instance of a class that matches the PEM type and implements - * {@link DEREncodable}, as follows: + * {@link BinaryEncodable}, as follows: *
For {@code PublicKey} and {@code PrivateKey} types, an algorithm-specific - * subclass is returned if the algorithm is supported. For example, an - * {@code ECPublicKey} or an {@code ECPrivateKey} for Elliptic Curve keys. - * - *
If the PEM type does not have a corresponding class, - * {@code decode(String)} and {@code decode(InputStream)} will return a - * {@code PEM} object. + *
If the PEM type has no corresponding class, {@code decode(String)} and + * {@code decode(InputStream)} will return a {@code PEM} object. * *
The {@link #decode(String, Class)} and {@link #decode(InputStream, Class)} - * methods take a class parameter that specifies the type of {@code DEREncodable} - * to return. These methods are useful for avoiding casts when the PEM type is - * known, or when extracting a specific type if there is more than one option. - * For example, if the PEM contains both a public and private key, specifying - * {@code PrivateKey.class} returns only the private key. - * If the class parameter specifies {@code X509EncodedKeySpec.class}, the - * public key encoding is returned as an instance of {@code X509EncodedKeySpec} - * class. Any type of PEM data can be decoded into a {@code PEM} object by - * specifying {@code PEM.class}. If the class parameter does not match the PEM - * content, a {@code ClassCastException} is thrown. + * methods accept a class parameter specifying the desired {@code BinaryEncodable} + * type. These methods avoid the need for casting and are useful when multiple + * representations are possible. For example, if the PEM contains both public and + * private keys, specifying {@code PrivateKey.class} returns only the private key. + * If {@code X509EncodedKeySpec.class} is provided, the public key encoding is + * returned as a {@code X509EncodedKeySpec}. To retrieve a {@link PEM} object, + * use {@code PEM.class}. If the specified class does not + * match the PEM content, a {@code ClassCastException} is thrown. * *
In addition to the types listed above, these methods support the - * following PEM types and {@code DEREncodable} classes when specified as + * following PEM types and {@code BinaryEncodable} classes when specified as * parameters: *
A new {@code PEMDecoder} instance is created when configured - * with {@link #withFactory(Provider)} or {@link #withDecryption(char[])}. - * The {@link #withFactory(Provider)} method uses the specified provider - * to produce cryptographic objects from {@link KeyFactory} and - * {@link CertificateFactory}. The {@link #withDecryption(char[])} method configures the + * with {@link #withFactoriesOf(Provider)} or {@link #withDecryption(char[])}. + * The {@link #withFactoriesOf(Provider)} method uses the specified provider when + * obtaining {@link KeyFactory} and {@link CertificateFactory} instances used + * during decoding. The {@link #withDecryption(char[])} method configures the * decoder to decrypt and decode encrypted private key PEM data using the given - * password. If decryption fails, an {@link IllegalArgumentException} is thrown. - * If an encrypted private key PEM is processed by a decoder not configured - * for decryption, an {@link EncryptedPrivateKeyInfo} object is returned. - * A {@code PEMDecoder} configured for decryption will decode unencrypted PEM. + * password. If decryption fails, a {@link CryptoException} is thrown. + * If an encrypted PEM is processed by a decoder not configured + * for decryption, an {@link EncryptedPrivateKeyInfo} is returned. + * A {@code PEMDecoder} configured for decryption can also decode unencrypted PEM. + * + *
The {@code BinaryEncodable} interface may evolve. When using a decode method + * with {@code switch}, always include a {@code default} case rather than + * relying on the classes specified in the permits clause to remain fixed. + * An exhaustive {@code switch} may result in a {@link MatchException}. * *
This class is immutable and thread-safe. * @@ -127,14 +125,13 @@ import java.util.Objects; *
Example: configure decryption and a factory provider: * {@snippet lang = java: * PEMDecoder pd = PEMDecoder.of().withDecryption(password). - * withFactory(provider); - * DEREncodable pemData = pd.decode(privKeyPEM); - * } + * withFactoriesOf(provider); + * BinaryEncodable pemData = pd.decode(privKeyPEM); + *} * - * @implNote This implementation decodes RSA PRIVATE KEY as {@code PrivateKey}, - * X509 CERTIFICATE and X.509 CERTIFICATE as {@code X509Certificate}, - * and CRL as {@code X509CRL}. Other implementations may recognize - * additional PEM types. + * @implNote This implementation decodes non-encrypted RSA PRIVATE KEY as {@code PrivateKey}, + * X509 CERTIFICATE and X.509 CERTIFICATE as {@code X509Certificate}, and CRL as + * {@code X509CRL}. Other implementations may recognize additional PEM types. * * @see PEMEncoder * @see PEM @@ -149,7 +146,6 @@ import java.util.Objects; * * @since 25 */ - @PreviewFeature(feature = PreviewFeature.Feature.PEM_API) public final class PEMDecoder { private final Provider factory; @@ -159,24 +155,23 @@ public final class 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 + * Creates an instance with a specific provider and/or password. + * @param withFactory Key/Certificate factory provider + * @param withKeySpec PBEKeySpec for EncryptedPrivateKeyInfo decryption */ - private PEMDecoder(Provider withFactory, PBEKeySpec withPassword) { - keySpec = withPassword; + private PEMDecoder(Provider withFactory, PBEKeySpec withKeySpec) { + keySpec = withKeySpec; factory = withFactory; - if (withPassword != null) { + if (withKeySpec != null) { final var k = this.keySpec; CleanerFactory.cleaner().register(this, k::clearPassword); } } /** - * Returns an instance of {@code PEMDecoder}. + * Returns the default {@code PEMDecoder} instance. * - * @return a {@code PEMDecoder} instance + * @return the default {@code PEMDecoder} */ public static PEMDecoder of() { return PEM_DECODER; @@ -187,23 +182,21 @@ public final class PEMDecoder { * header and footer and proceed with decoding the base64 for the * appropriate type. */ - private DEREncodable decode(PEM pem) { - Base64.Decoder decoder = Base64.getMimeDecoder(); - + private BinaryEncodable decode(PEM pem) { try { return switch (pem.type()) { case Pem.PUBLIC_KEY -> { X509EncodedKeySpec spec = - new X509EncodedKeySpec(decoder.decode(pem.content())); + new X509EncodedKeySpec(pem.decode()); yield getKeyFactory( KeyUtil.getAlgorithm(spec.getEncoded())). generatePublic(spec); } case Pem.PRIVATE_KEY -> { - DEREncodable d; + BinaryEncodable d; PKCS8Key p8key = null; PKCS8EncodedKeySpec p8spec = null; - byte[] encoding = decoder.decode(pem.content()); + byte[] encoding = pem.decode(); try { p8key = new PKCS8Key(encoding); @@ -238,36 +231,37 @@ public final class PEMDecoder { } case Pem.ENCRYPTED_PRIVATE_KEY -> { byte[] p8 = null; - byte[] encoding = null; + var ekpi = new EncryptedPrivateKeyInfo(pem.decode()); + if (keySpec == null) { + yield ekpi; + } try { - encoding = decoder.decode(pem.content()); - var ekpi = new EncryptedPrivateKeyInfo(encoding); - if (keySpec == null) { - yield ekpi; - } p8 = Pem.decryptEncoding(ekpi, keySpec); - yield Pem.toDEREncodable(p8, true, factory); + } catch (GeneralSecurityException e) { + throw new CryptoException(e); + } + try { + yield Pem.toPKCS8Encodable(p8, factory); } finally { Reference.reachabilityFence(this); - KeyUtil.clear(encoding, p8); + KeyUtil.clear(p8); } } case Pem.CERTIFICATE, Pem.X509_CERTIFICATE, Pem.X_509_CERTIFICATE -> { CertificateFactory cf = getCertFactory("X509"); yield (X509Certificate) cf.generateCertificate( - new ByteArrayInputStream(decoder.decode(pem.content()))); + new ByteArrayInputStream(pem.decode())); } case Pem.X509_CRL, Pem.CRL -> { CertificateFactory cf = getCertFactory("X509"); yield (X509CRL) cf.generateCRL( - new ByteArrayInputStream(decoder.decode(pem.content()))); + new ByteArrayInputStream(pem.decode())); } case Pem.RSA_PRIVATE_KEY -> { KeyFactory kf = getKeyFactory("RSA"); yield kf.generatePrivate( - RSAPrivateCrtKeyImpl.getKeySpec(decoder.decode( - pem.content()))); + RSAPrivateCrtKeyImpl.getKeySpec(pem.decode())); } default -> pem; }; @@ -277,155 +271,182 @@ public final class PEMDecoder { } /** - * Decodes and returns a {@code DEREncodable} from the given {@code String}. + * Decodes and returns a {@code BinaryEncodable} from the given {@code String}. * *
This method reads the {@code String} until PEM data is found - * or the end of the {@code String} is reached. If no PEM data is found, + * or the end of the {@code String} is reached. If no PEM data is found, * an {@code IllegalArgumentException} is thrown. * - *
A {@code DEREncodable} will be returned that best represents the - * decoded data. If the PEM type is not supported, a {@code PEM} object is + *
A {@code BinaryEncodable} is returned that best represents the + * decoded content. If the PEM type is not supported, a {@code PEM} object is * returned containing the type identifier, Base64-encoded data, and any - * leading data preceding the PEM header. For {@code DEREncodable} types - * other than {@code PEM}, leading data is ignored and not returned as part - * of the {@code DEREncodable} object. + * leading data preceding the PEM header. For {@code BinaryEncodable} types + * other than {@code PEM}, leading data is ignored. * - *
Input consumed by this method is read in as + *
The input is interpreted as * {@link java.nio.charset.StandardCharsets#UTF_8 UTF-8}. * * @param str a {@code 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 {@code null} + * @return a {@code BinaryEncodable} + * @throws IllegalArgumentException if decoding fails or no PEM data is found + * @throws NullPointerException if {@code str} is {@code null} + * @throws CryptoException if an error occurs during decryption + * + * @since 27 */ - public DEREncodable decode(String str) { + public BinaryEncodable decode(String str) { Objects.requireNonNull(str); + byte[] encoding = null; try { - return decode(new ByteArrayInputStream( - str.getBytes(StandardCharsets.UTF_8))); + encoding = str.getBytes(StandardCharsets.UTF_8); + return decode(new ByteArrayInputStream(encoding)); } catch (IOException e) { // With all data contained in the String, there are no IO ops. throw new IllegalArgumentException(e); + } finally { + KeyUtil.clear(encoding); } } /** - * Decodes and returns a {@code DEREncodable} from the given + * Decodes and returns a {@code BinaryEncodable} from the given * {@code InputStream}. * *
This method reads from the {@code InputStream} until the end of * a 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}. + * the stream position may become inconsistent. Further decoding + * operations on the same {@code InputStream} are not recommended. * - *
A {@code DEREncodable} will be returned that best represents the - * decoded data. If the PEM type is not supported, a {@code PEM} object is + *
A {@code BinaryEncodable} is returned that best represents the + * decoded content. If the PEM type is not supported, a {@code PEM} object is * returned containing the type identifier, Base64-encoded data, and any - * leading data preceding the PEM header. For {@code DEREncodable} types - * other than {@code PEM}, leading data is ignored and not returned as part - * of the {@code DEREncodable} object. + * leading data preceding the PEM header. For {@code BinaryEncodable} types + * other than {@code PEM}, leading data is ignored. * *
If no PEM data is found, an {@code EOFException} is thrown. * * @param is {@code 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 no PEM data found or unexpectedly reached the - * end of the {@code InputStream} - * @throws IllegalArgumentException on error in decoding - * @throws NullPointerException when {@code is} is {@code null} + * @return a {@code BinaryEncodable} + * @throws IOException if an I/O error occurs or PEM syntax is invalid + * @throws EOFException if no PEM data is found or the stream ends unexpectedly + * @throws IllegalArgumentException if decoding fails + * @throws NullPointerException if {@code InputStream} is {@code null} + * @throws CryptoException if an error occurs during decryption + * + * @since 27 */ - public DEREncodable decode(InputStream is) throws IOException { + public BinaryEncodable decode(InputStream is) throws IOException { Objects.requireNonNull(is); PEM 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 be an appropriate class for - * the PEM type. - * - *
This method reads the {@code String} until PEM data is found - * or the end of the {@code String} is reached. If no PEM data is found, - * an {@code IllegalArgumentException} is thrown. - * - *
If the class parameter is {@code PEM.class}, a {@code PEM} object is - * returned containing the type identifier, Base64-encoded data, and any - * leading data preceding the PEM header. For {@code DEREncodable} types - * other than {@code PEM}, leading data is ignored and not returned as part - * of the {@code DEREncodable} object. - * - *
Input consumed by this method is read in as
- * {@link java.nio.charset.StandardCharsets#UTF_8 UTF-8}.
- *
- * @param class type parameter that extends {@code DEREncodable}
- * @param str the {@code String} containing PEM data
- * @param tClass the returned object class that extends or 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} does not represent the PEM type
- * @throws NullPointerException when any input values are {@code null}
- */
- public S decode(String str, Class tClass) {
- Objects.requireNonNull(str);
+ BinaryEncodable be = null;
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);
+ be = decode(pem);
+ return be;
+ } finally {
+ if (be != pem) {
+ pem.clear();
+ }
}
}
/**
- * Decodes and returns a {@code DEREncodable} of the specified class for the
- * given {@code InputStream}. {@code tClass} must be an appropriate class
- * for the PEM type.
+ * Decodes and returns a {@code BinaryEncodable} of the specified class from
+ * the given PEM string.
+ *
+ *
{@code tClass} must be an appropriate class for the PEM type. + * + *
This method reads the {@code String} until PEM data is found or the end + * of the {@code String} is reached. If no PEM data is found, an + * {@code IllegalArgumentException} is thrown. + * + *
If {@code tClass} is {@code PEM.class}, a {@code PEM} object is returned + * containing the type identifier, Base64-encoded data, and any leading data + * preceding the PEM header. For {@code BinaryEncodable} types other than + * {@code PEM}, leading data is ignored. + * + *
The input is interpreted as
+ * {@link java.nio.charset.StandardCharsets#UTF_8 UTF-8}.
+ *
+ * @param class type parameter that extends {@code BinaryEncodable}
+ * @param str the {@code String} containing PEM data
+ * @param tClass the returned object class that extends or implements
+ * {@code BinaryEncodable}
+ * @return a {@code BinaryEncodable} specified by {@code tClass}
+ * @throws IllegalArgumentException on error in decoding or no PEM data found
+ * @throws ClassCastException if {@code tClass} does not represent the PEM type
+ * @throws NullPointerException if any input values are {@code null}
+ * @throws CryptoException if an error occurs during decryption
+ *
+ * @since 27
+ */
+ public S decode(String str, Class tClass) {
+ Objects.requireNonNull(str);
+ byte[] encoding = null;
+ try {
+ encoding = str.getBytes(StandardCharsets.UTF_8);
+ return decode(new ByteArrayInputStream(encoding), tClass);
+ } catch (IOException e) {
+ // With all data contained in the String, there are no IO ops.
+ throw new IllegalArgumentException(e);
+ } finally {
+ KeyUtil.clear(encoding);
+ }
+ }
+
+ /**
+ * Decodes and returns a {@code BinaryEncodable} of the specified class from
+ * the given {@code InputStream}.
+ *
+ *
{@code tClass} must be an appropriate class for the PEM type. * *
This method reads from the {@code InputStream} until the end of * a 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}. + * the stream position may become inconsistent. Further decoding + * operations on the same {@code InputStream} are not recommended. * - *
If the class parameter is {@code PEM.class}, a {@code PEM} object is - * returned containing the type identifier, Base64-encoded data, and any - * leading data preceding the PEM header. For {@code DEREncodable} types - * other than {@code PEM}, leading data is ignored and not returned as part - * of the {@code DEREncodable} object. + *
If {@code tClass} is {@code PEM.class}, a {@code PEM} object is returned + * containing the type identifier, Base64-encoded data, and any leading data + * preceding the PEM header. For {@code BinaryEncodable} types other than + * {@code PEM}, leading data is ignored. * *
If no PEM data is found, an {@code EOFException} is thrown.
*
- * @param class type parameter that extends {@code DEREncodable}
+ * @param class type parameter that extends {@code BinaryEncodable}
* @param is an {@code InputStream} containing PEM data
* @param tClass the returned object class that extends or 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 no PEM data found or unexpectedly reached the
- * end of the {@code InputStream}
- * @throws IllegalArgumentException on error in decoding
+ * {@code BinaryEncodable}
+ * @return a {@code BinaryEncodable} of type {@code tClass}
+ * @throws IOException if an I/O error occurs or PEM syntax is invalid
+ * @throws EOFException if no PEM data is found or the stream ends unexpectedly
+ * @throws IllegalArgumentException if decoding fails
* @throws ClassCastException if {@code tClass} does not represent the PEM type
- * @throws NullPointerException when any input values are {@code null}
+ * @throws NullPointerException if any input values are {@code null}
+ * @throws CryptoException if an error occurs during decryption
*
- * @see #decode(InputStream)
+ * @see #decode(InputStream)
* @see #decode(String, Class)
+ *
+ * @since 27
*/
- public S decode(InputStream is, Class tClass)
+ public S decode(InputStream is, Class tClass)
throws IOException {
Objects.requireNonNull(is);
Objects.requireNonNull(tClass);
PEM pem = Pem.readPEM(is);
- if (tClass.isAssignableFrom(PEM.class)) {
+ if (tClass == PEM.class) {
return tClass.cast(pem);
+ } else if (tClass == BinaryEncodable.class) {
+ pem.clear();
+ throw new ClassCastException("BinaryEncodable is not a PEM type");
+ }
+
+ BinaryEncodable so;
+ try {
+ so = decode(pem);
+ } finally {
+ pem.clear();
}
- DEREncodable so = decode(pem);
/*
* If the object is a KeyPair, check if the tClass is set to class
@@ -441,6 +462,9 @@ public final class PEMDecoder {
if ((PublicKey.class).isAssignableFrom(tClass) ||
(X509EncodedKeySpec.class).isAssignableFrom(tClass)) {
so = kp.getPublic();
+ if (kp.getPrivate() instanceof PKCS8Key p8Key) {
+ KeyUtil.clear(p8Key);
+ }
}
}
@@ -470,6 +494,10 @@ public final class PEMDecoder {
throw new ClassCastException("Invalid KeySpec " +
"specified: " + tClass.getName() + " for key " +
key.getClass().getName());
+ } finally {
+ if (key instanceof PKCS8Key p8Key) {
+ KeyUtil.clear(p8Key);
+ }
}
}
@@ -509,19 +537,29 @@ public final class PEMDecoder {
* from the specified {@code Provider} to produce cryptographic objects.
* Any errors using the {@code Provider} will occur during decoding.
*
- * @param provider the factory provider
+ * @param provider the factory {@code Provider}
* @return a new {@code PEMDecoder} instance configured with the {@code Provider}
* @throws NullPointerException if {@code provider} is {@code null}
+ *
+ * @since 27
*/
- public PEMDecoder withFactory(Provider provider) {
+ public PEMDecoder withFactoriesOf(Provider provider) {
Objects.requireNonNull(provider);
- return new PEMDecoder(provider, keySpec);
+ if (keySpec == null) {
+ return new PEMDecoder(provider, null);
+ }
+ char[] passwd = keySpec.getPassword();
+ try {
+ return new PEMDecoder(provider, new PBEKeySpec(passwd));
+ } finally {
+ KeyUtil.clear(passwd);
+ }
}
/**
* Returns a copy of this {@code PEMDecoder} that decodes and decrypts
* encrypted private keys using the specified password.
- * Non-encrypted PEM can also be decoded from this instance.
+ * Unencrypted PEM can also be decoded by the returned instance.
*
* @param password the password to decrypt the encrypted PEM data. This array
* is cloned and stored in the new instance.
diff --git a/src/java.base/share/classes/java/security/PEMEncoder.java b/src/java.base/share/classes/java/security/PEMEncoder.java
index 62a0942caf7..211b47008a5 100644
--- a/src/java.base/share/classes/java/security/PEMEncoder.java
+++ b/src/java.base/share/classes/java/security/PEMEncoder.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2025, 2026, 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
@@ -26,6 +26,8 @@
package java.security;
import jdk.internal.javac.PreviewFeature;
+
+import jdk.internal.ref.CleanerFactory;
import sun.security.pkcs.PKCS8Key;
import sun.security.util.KeyUtil;
import sun.security.util.Pem;
@@ -35,7 +37,6 @@ 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;
@@ -44,22 +45,19 @@ import java.util.Objects;
* {@code PEMEncoder} implements an encoder for Privacy-Enhanced Mail (PEM)
* data. PEM is a textual encoding used to store and transfer cryptographic
* 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-encoded binary encoding enclosed by a type-identifying header
+ * lists (CRLs). It is defined in RFC 1421 and RFC 7468. PEM consists of a
+ * Base64-encoded content enclosed by a type-identifying header
* and footer.
*
*
Encoding can be performed on cryptographic objects that - * implement {@link DEREncodable}. The {@link #encode(DEREncodable)} - * and {@link #encodeToString(DEREncodable)} methods encode a {@code DEREncodable} + * implement {@link BinaryEncodable}. The {@link #encode(BinaryEncodable)} + * and {@link #encodeToString(BinaryEncodable)} methods encode a {@code BinaryEncodable} * into PEM and return the data in a byte array or {@code String}. * *
Private keys can be encrypted and encoded by configuring a * {@code PEMEncoder} with the {@link #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 {@link EncryptedPrivateKeyInfo} object can be encoded - * directly to PEM by passing it to the {@code encode} or - * {@code encodeToString} methods. + * configured to encrypt the key with that password. * *
PKCS #8 v2.0 defines the ASN.1 OneAsymmetricKey structure, which may * contain both private and public keys. @@ -72,24 +70,24 @@ import java.util.Objects; * {@link PEM#type()}. The value returned by {@link PEM#leadingData()} is not * included in the output. * - *
The following lists the supported {@code DEREncodable} classes and - * the PEM types they encode as: + *
The following lists the supported {@code BinaryEncodable} classes and + * the PEM types they encode to: *
When used with a {@code PEMEncoder} instance configured for encryption: *
This class is immutable and thread-safe. @@ -108,7 +106,6 @@ import java.util.Objects; * * @implNote Implementations may support additional PEM types. * - * * @see PEMDecoder * @see PEM * @see EncryptedPrivateKeyInfo @@ -128,21 +125,24 @@ public final class PEMEncoder { // Singleton instance of PEMEncoder private static final PEMEncoder PEM_ENCODER = new PEMEncoder(null); // PBE key for encryption - private final Key key; + private final SecretKey key; /** - * Create an encrypted {@code PEMEncoder} instance. + * Creates a PEMEncoder instance configured for the given keySpec. */ private PEMEncoder(PBEKeySpec keySpec) { if (keySpec != null) { try { key = SecretKeyFactory.getInstance(Pem.DEFAULT_ALGO). generateSecret(keySpec); + final SecretKey k = this.key; + CleanerFactory.cleaner().register(this, + () -> KeyUtil.destroySecretKeys(k)); } catch (GeneralSecurityException e) { - throw new IllegalArgumentException("Operation failed: " + + throw new CryptoException("Operation failed: " + "unable to generate key or locate a valid algorithm. " + "Check the jdk.epkcs8.defaultAlgorithm security " + - "property for a valid configuration.", e); + "property for a valid configuration", e); } } else { key = null; @@ -159,63 +159,85 @@ public final class PEMEncoder { } /** - * Encodes the specified {@code DEREncodable} and returns a PEM-encoded + * Encodes the specified {@code BinaryEncodable} and returns a PEM-encoded * string. * - * @param de the {@code DEREncodable} to be encoded + * @param be the {@code BinaryEncodable} to encode * @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} + * @throws IllegalArgumentException if {@code be} has no encoding, is + * an unsupported class, or cannot be used with encryption + * @throws NullPointerException if {@code be} is {@code null} + * @throws CryptoException if an error occurs during encryption * @see #withEncryption(char[]) + * + * @since 27 */ - public String encodeToString(DEREncodable de) { - Objects.requireNonNull(de); - return switch (de) { - case PublicKey pu -> buildKey(pu.getEncoded(), null); - case PrivateKey pr -> { - byte[] encoding = pr.getEncoded(); - try { - yield buildKey(null, encoding); - } finally { - KeyUtil.clear(encoding); - } + public String encodeToString(BinaryEncodable be) { + Objects.requireNonNull(be); + if (be instanceof PEM pem) { + if (key != null) { + throw new IllegalArgumentException("PEM cannot be " + + "encrypted"); } + return pem.toString(); + } + return KeyUtil.clear(encode(be), + e -> new String(e, StandardCharsets.ISO_8859_1)); + } + + /** + * Encodes the specified {@code BinaryEncodable} and returns a PEM-encoded + * byte array. + * + * @param be the {@code BinaryEncodable} to encode + * @return a PEM-encoded byte array + * @throws IllegalArgumentException if {@code be} has no encoding, is + * an unsupported class, or cannot be used with encryption + * @throws NullPointerException if {@code be} is {@code null} + * @throws CryptoException if an error occurs during encryption + * @see #withEncryption(char[]) + * + * @since 27 + */ + public byte[] encode(BinaryEncodable be) { + return switch (be) { + case PublicKey pu -> buildKey(pu.getEncoded(), null); + case PrivateKey pr -> + KeyUtil.clear(pr.getEncoded(), e -> buildKey(null, e)); case KeyPair kp -> { - byte[] encoding = null; - try { - if (kp.getPublic() == null) { - throw new IllegalArgumentException("KeyPair does not " + - "contain PublicKey."); - } - if (kp.getPrivate() == null) { - throw new IllegalArgumentException("KeyPair does not " + - "contain PrivateKey."); - } - encoding = kp.getPrivate().getEncoded(); - if (encoding == null || encoding.length == 0) { - throw new IllegalArgumentException("PrivateKey is " + - "null or has no encoding."); - } - yield buildKey(kp.getPublic().getEncoded(), encoding); - } finally { - KeyUtil.clear(encoding); + if (kp.getPublic() == null) { + throw new IllegalArgumentException("KeyPair does not " + + "contain PublicKey"); } + if (kp.getPrivate() == null) { + throw new IllegalArgumentException("KeyPair does not " + + "contain PrivateKey"); + } + byte[] pubEncoding = kp.getPublic().getEncoded(); + if (pubEncoding == null || pubEncoding.length == 0) { + throw new IllegalArgumentException("PublicKey is " + + "null or has no encoding"); + } + byte[] encoding = kp.getPrivate().getEncoded(); + if (encoding == null || encoding.length == 0) { + throw new IllegalArgumentException("PrivateKey is " + + "null or has no encoding"); + } + yield KeyUtil.clear(encoding, e -> buildKey(pubEncoding, e)); } case X509EncodedKeySpec x -> buildKey(x.getEncoded(), null); - case PKCS8EncodedKeySpec p -> buildKey(null, p.getEncoded()); + case PKCS8EncodedKeySpec p -> + KeyUtil.clear(p.getEncoded(), e -> buildKey(null, e)); case EncryptedPrivateKeyInfo epki -> { - byte[] encoding = null; if (key != null) { throw new IllegalArgumentException( "EncryptedPrivateKeyInfo cannot be encrypted"); } try { - encoding = epki.getEncoded(); - yield Pem.pemEncoded(Pem.ENCRYPTED_PRIVATE_KEY, encoding); + yield KeyUtil.clear(epki.getEncoded(), + e -> Pem.pemEncodedFromDER(Pem.ENCRYPTED_PRIVATE_KEY, e)); } catch (IOException e) { throw new IllegalArgumentException(e); - } finally { - KeyUtil.clear(encoding); } } case X509Certificate c -> { @@ -224,7 +246,7 @@ public final class PEMEncoder { "cannot be encrypted"); } try { - yield Pem.pemEncoded(Pem.CERTIFICATE, c.getEncoded()); + yield Pem.pemEncodedFromDER(Pem.CERTIFICATE, c.getEncoded()); } catch (CertificateEncodingException e) { throw new IllegalArgumentException(e); } @@ -235,7 +257,7 @@ public final class PEMEncoder { "encrypted"); } try { - yield Pem.pemEncoded(Pem.X509_CRL, crl.getEncoded()); + yield Pem.pemEncodedFromDER(Pem.X509_CRL, crl.getEncoded()); } catch (CRLException e) { throw new IllegalArgumentException(e); } @@ -245,53 +267,39 @@ public final class PEMEncoder { throw new IllegalArgumentException("PEM cannot be " + "encrypted"); } - yield Pem.pemEncoded(rec); + yield rec.toTextualByteArray(); } default -> throw new IllegalArgumentException("PEM does not " + - "support " + de.getClass().getCanonicalName()); + "support " + be.getClass().getCanonicalName()); }; } /** - * Encodes the specified {@code DEREncodable} and returns a PEM-encoded - * 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 copy of this PEMEncoder that encrypts and encodes - * using the specified password and default encryption algorithm. + * Returns a copy of this {@code PEMEncoder} configured to encrypt and + * encode using the specified password and the default encryption algorithm. * *
Only {@code PrivateKey}, {@code KeyPair}, and * {@code PKCS8EncodedKeySpec} objects can be encoded with this newly - * configured instance. Encoding other {@code DEREncodable} objects will - * throw an {@code IllegalArgumentException}. + * configured instance. Attempting to encode other {@code BinaryEncodable} + * objects will throw an {@code IllegalArgumentException}. + * + *
To use non-default encryption parameters or a different provider, use + * an {@code encrypt} method in {@link EncryptedPrivateKeyInfo}, then pass + * the resulting object to {@link #encode(BinaryEncodable)}. * * @implNote The {@code jdk.epkcs8.defaultAlgorithm} security property * defines the default encryption algorithm. The {@code AlgorithmParameterSpec} - * defaults are determined by the provider. To use non-default encryption - * parameters, or to encrypt with a different encryption provider, use - * {@link EncryptedPrivateKeyInfo#encrypt(DEREncodable, Key, - * String, AlgorithmParameterSpec, Provider, SecureRandom)} and use the - * returned object with {@link #encode(DEREncodable)}. + * defaults are determined by the provider. * * @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 if password is {@code null} - * @throws IllegalArgumentException if generating the encryption key fails + * @throws NullPointerException if {@code password} is {@code null} + * @throws CryptoException if generating the encryption key fails */ public PEMEncoder withEncryption(char[] password) { - Objects.requireNonNull(password, "password cannot be null."); + Objects.requireNonNull(password, "password cannot be null"); PBEKeySpec keySpec = new PBEKeySpec(password); try { return new PEMEncoder(keySpec); @@ -301,14 +309,12 @@ public final class PEMEncoder { } /** - * Build PEM encoding. - * - * privateKeyEncoding will be zeroed when the method returns + * Build the PEM encoding for AsymmetricKey and KeyPair */ - private String buildKey(byte[] publicEncoding, byte[] privateEncoding) { + private byte[] buildKey(byte[] publicEncoding, byte[] privateEncoding) { if (publicEncoding == null && privateEncoding == null) { throw new IllegalArgumentException("No encoded data given by the " + - "DEREncodable."); + "BinaryEncodable"); } if (publicEncoding != null && publicEncoding.length == 0) { @@ -322,35 +328,36 @@ public final class PEMEncoder { } if (key != null && privateEncoding == null) { - throw new IllegalArgumentException("This DEREncodable cannot " + - "be encrypted."); + throw new IllegalArgumentException("This BinaryEncodable cannot " + + "be encrypted"); } // X509 only if (publicEncoding != null && privateEncoding == null) { - return Pem.pemEncoded(Pem.PUBLIC_KEY, publicEncoding); + return Pem.pemEncodedFromDER(Pem.PUBLIC_KEY, publicEncoding); } byte[] encoding = null; PKCS8EncodedKeySpec p8KeySpec = null; try { if (publicEncoding == null) { - encoding = privateEncoding; + encoding = privateEncoding.clone(); } else { encoding = PKCS8Key.getEncoded(publicEncoding, privateEncoding); } if (key != null) { p8KeySpec = new PKCS8EncodedKeySpec(encoding); + KeyUtil.clear(encoding); encoding = EncryptedPrivateKeyInfo.encrypt(p8KeySpec, key, Pem.DEFAULT_ALGO, null, null, null). getEncoded(); } if (encoding.length == 0) { throw new IllegalArgumentException("No private key encoding " + - "given by the DEREncodable."); + "given by the BinaryEncodable"); } - return Pem.pemEncoded( + return Pem.pemEncodedFromDER( (key == null ? Pem.PRIVATE_KEY : Pem.ENCRYPTED_PRIVATE_KEY), encoding); } catch (IOException e) { diff --git a/src/java.base/share/classes/java/security/cert/X509CRL.java b/src/java.base/share/classes/java/security/cert/X509CRL.java index d19618f81ed..2745f0e377b 100644 --- a/src/java.base/share/classes/java/security/cert/X509CRL.java +++ b/src/java.base/share/classes/java/security/cert/X509CRL.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, 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 non-sealed class X509CRL extends CRL implements X509Extension, DEREncodable { +public abstract non-sealed class X509CRL extends CRL implements X509Extension, BinaryEncodable { private transient X500Principal issuerPrincipal; diff --git a/src/java.base/share/classes/java/security/cert/X509Certificate.java b/src/java.base/share/classes/java/security/cert/X509Certificate.java index fe4a472dead..bd19f3d33d0 100644 --- a/src/java.base/share/classes/java/security/cert/X509Certificate.java +++ b/src/java.base/share/classes/java/security/cert/X509Certificate.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, 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 @@ -108,7 +108,7 @@ import java.util.List; */ public abstract non-sealed class X509Certificate extends Certificate - implements X509Extension, DEREncodable { + implements X509Extension, BinaryEncodable { @java.io.Serial private static final long serialVersionUID = -2491127588187038216L; diff --git a/src/java.base/share/classes/java/security/spec/PKCS8EncodedKeySpec.java b/src/java.base/share/classes/java/security/spec/PKCS8EncodedKeySpec.java index 0518e6ed272..319f1ad4cc8 100644 --- a/src/java.base/share/classes/java/security/spec/PKCS8EncodedKeySpec.java +++ b/src/java.base/share/classes/java/security/spec/PKCS8EncodedKeySpec.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, 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,7 @@ package java.security.spec; -import java.security.DEREncodable; +import java.security.BinaryEncodable; /** * This class represents the ASN.1 encoding of a private key, @@ -73,7 +73,7 @@ import java.security.DEREncodable; */ public non-sealed class PKCS8EncodedKeySpec extends EncodedKeySpec implements - DEREncodable { + BinaryEncodable { /** * Creates a new {@code PKCS8EncodedKeySpec} with the given encoded key. * diff --git a/src/java.base/share/classes/java/security/spec/X509EncodedKeySpec.java b/src/java.base/share/classes/java/security/spec/X509EncodedKeySpec.java index 6d0f105e64f..875660a8ed4 100644 --- a/src/java.base/share/classes/java/security/spec/X509EncodedKeySpec.java +++ b/src/java.base/share/classes/java/security/spec/X509EncodedKeySpec.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, 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,7 @@ package java.security.spec; -import java.security.DEREncodable; +import java.security.BinaryEncodable; /** * This class represents the ASN.1 encoding of a public key, @@ -52,7 +52,7 @@ import java.security.DEREncodable; */ public non-sealed class X509EncodedKeySpec extends EncodedKeySpec implements - DEREncodable { + BinaryEncodable { /** * Creates a new {@code X509EncodedKeySpec} with the given encoded key. * diff --git a/src/java.base/share/classes/javax/crypto/CryptoException.java b/src/java.base/share/classes/javax/crypto/CryptoException.java new file mode 100644 index 00000000000..367417abf9d --- /dev/null +++ b/src/java.base/share/classes/javax/crypto/CryptoException.java @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2026, 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 javax.crypto; + +import jdk.internal.javac.PreviewFeature; + +/** + * Thrown to indicate a cryptographic failure during processing. + * + *
This exception represents a general cryptographic error. It is typically + * used for unrecoverable failures related to + * {@link java.security.GeneralSecurityException} in contexts where checked + * exceptions are not desired. + * + *
This exception is not intended to represent internal provider errors, + * which should be reported using {@link java.security.ProviderException}. + * + * @since 27 + */ +@PreviewFeature(feature = PreviewFeature.Feature.PEM_API) +public final class CryptoException extends RuntimeException { + + @java.io.Serial + private static final long serialVersionUID = -6824337376392797817L; + + /** + * Constructs a new {@code CryptoException} with {@code null} as its detail + * message. The cause is not initialized and may subsequently be initialized + * by a call to {@link #initCause(Throwable)}. + */ + public CryptoException() { + super(); + } + + /** + * Constructs a new {@code CryptoException} with the specified detail message. + * The cause is not initialized and may subsequently be initialized by a + * call to {@link #initCause(Throwable)}. + * + * @param message the detail message. The detail message is saved for later + * retrieval by the {@link #getMessage()} method. + */ + public CryptoException(String message) { + super(message); + } + + /** + * Constructs a new {@code CryptoException} with the specified detail + * message and cause. + * + *
Note that the detail message associated with {@code cause} is not
+ * automatically incorporated in this exception's detail message.
+ *
+ * @param message the detail message. The detail message is saved for later
+ * retrieval by the {@link #getMessage()} method.
+ * @param cause the cause. The cause is saved for later retrieval by the
+ * {@link #getCause()} method. A {@code null} value is permitted
+ * and indicates that the cause is nonexistent or unknown.
+ */
+ public CryptoException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ /**
+ * Constructs a new {@code CryptoException} with the specified cause and a detail
+ * message of {@code (cause == null ? null : cause.toString())}, which
+ * typically contains the class and detail message of {@code cause}.
+ *
+ * @param cause the cause. The cause is saved for later retrieval by the
+ * {@link #getCause()} method. A {@code null} value is permitted
+ * and indicates that the cause is nonexistent or unknown.
+ */
+ public CryptoException(Throwable cause) {
+ super(cause);
+ }
+}
diff --git a/src/java.base/share/classes/javax/crypto/EncryptedPrivateKeyInfo.java b/src/java.base/share/classes/javax/crypto/EncryptedPrivateKeyInfo.java
index dc3c4c14b27..632c81eab16 100644
--- a/src/java.base/share/classes/javax/crypto/EncryptedPrivateKeyInfo.java
+++ b/src/java.base/share/classes/javax/crypto/EncryptedPrivateKeyInfo.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2001, 2025, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2001, 2026, 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
@@ -60,7 +60,7 @@ import java.util.Objects;
* @since 1.4
*/
-public non-sealed class EncryptedPrivateKeyInfo implements DEREncodable {
+public non-sealed class EncryptedPrivateKeyInfo implements BinaryEncodable {
// The "encryptionAlgorithm" is stored in either the algid or
// the params field. Precisely, if this object is created by
@@ -221,7 +221,7 @@ public non-sealed class EncryptedPrivateKeyInfo implements DEREncodable {
}
/**
- * Create an EncryptedPrivateKeyInfo object from the given components
+ * Create an EncryptedPrivateKeyInfo object from the given components.
*/
private EncryptedPrivateKeyInfo(byte[] encoded, byte[] eData,
AlgorithmId id, AlgorithmParameters p) {
@@ -265,8 +265,8 @@ public non-sealed class EncryptedPrivateKeyInfo implements DEREncodable {
}
/**
- * Extract the enclosed PKCS8EncodedKeySpec object from the
- * encrypted data and return it.
+ * Extracts the enclosed PKCS8EncodedKeySpec object from the
+ * encrypted data and returns it.
*
Note: In order to successfully retrieve the enclosed
* PKCS8EncodedKeySpec object, {@code cipher} needs
* to be initialized to either Cipher.DECRYPT_MODE or
@@ -275,7 +275,7 @@ public non-sealed class EncryptedPrivateKeyInfo implements DEREncodable {
*
* @param cipher the initialized {@code Cipher} object which will be
* used for decrypting the encrypted data.
- * @return the PKCS8EncodedKeySpec object.
+ * @return the PKCS8EncodedKeySpec object
* @exception NullPointerException if {@code cipher} is {@code null}.
* @exception InvalidKeySpecException if the given cipher is
* inappropriate for the encrypted data or the encrypted
@@ -283,7 +283,7 @@ public non-sealed class EncryptedPrivateKeyInfo implements DEREncodable {
*/
public PKCS8EncodedKeySpec getKeySpec(Cipher cipher)
throws InvalidKeySpecException {
- byte[] encoded;
+ byte[] encoded = null;
try {
encoded = cipher.doFinal(encryptedData);
return pkcs8EncodingToSpec(encoded);
@@ -292,6 +292,8 @@ public non-sealed class EncryptedPrivateKeyInfo implements DEREncodable {
IllegalStateException ex) {
throw new InvalidKeySpecException(
"Cannot retrieve the PKCS8EncodedKeySpec", ex);
+ } finally {
+ KeyUtil.clear(encoded);
}
}
@@ -338,7 +340,7 @@ public non-sealed class EncryptedPrivateKeyInfo implements DEREncodable {
/**
* Creates an {@code EncryptedPrivateKeyInfo} by encrypting the specified
- * {@code DEREncodable}. A valid password-based encryption (PBE) algorithm
+ * {@code BinaryEncodable}. A valid password-based encryption (PBE) algorithm
* and password must be specified.
*
*
The format of the PBE algorithm string is described in the @@ -346,7 +348,7 @@ public non-sealed class EncryptedPrivateKeyInfo implements DEREncodable { * Cipher Algorithms section of the Java Security Standard Algorithm Names * Specification. * - * @param de the {@code DEREncodable} to encrypt. Supported types include + * @param be the {@code BinaryEncodable} to encrypt. Supported types include * {@code PrivateKey}, {@code KeyPair}, and {@code PKCS8EncodedKeySpec}. * @param password the password used for PBE encryption. This array is cloned * before use. @@ -354,68 +356,73 @@ public non-sealed class EncryptedPrivateKeyInfo implements DEREncodable { * @param params the {@code AlgorithmParameterSpec} used for encryption. If * {@code null}, the provider’s default parameters are applied. * @param provider the {@code Provider} for {@code SecretKeyFactory} and - * {@code Cipher} operations. If {@code null}, provider - * defaults are used. + * {@code Cipher} operations. If {@code null}, the default + * provider list is used. * @return an {@code EncryptedPrivateKeyInfo} - * @throws NullPointerException if {@code de}, {@code password}, or + * @throws NullPointerException if {@code be}, {@code password}, or * {@code algorithm} is {@code null} - * @throws IllegalArgumentException if {@code de} is an unsupported - * {@code DEREncodable}, if an error occurs while generating the + * @throws IllegalArgumentException if {@code be} is an unsupported + * {@code BinaryEncodable} or has no encoding + * @throws CryptoException if an error occurs while generating the * PBE key, if {@code algorithm} or {@code params} are * not supported by any provider, or if an error occurs during - * encryption. + * encryption * - * @since 26 + * @since 27 */ @PreviewFeature(feature = PreviewFeature.Feature.PEM_API) - public static EncryptedPrivateKeyInfo encrypt(DEREncodable de, + public static EncryptedPrivateKeyInfo encrypt(BinaryEncodable be, char[] password, String algorithm, AlgorithmParameterSpec params, Provider provider) { - Objects.requireNonNull(de, "a key must be specified."); - Objects.requireNonNull(password, "a password must be specified."); - Objects.requireNonNull(algorithm, "an algorithm must be specified."); + Objects.requireNonNull(be, "a key must be specified"); + Objects.requireNonNull(password, "a password must be specified"); + Objects.requireNonNull(algorithm, "an algorithm must be specified"); char[] passwd = password.clone(); - byte[] encoding = getEncoding(de); + byte[] encoding = null; + SecretKey sk = null; try { - return encryptImpl(encoding, algorithm, - generateSecretKey(passwd, algorithm, provider), params, - provider, null); + encoding = getEncoding(be); + sk = generateSecretKey(passwd, algorithm, provider); + return encryptImpl(encoding, algorithm, sk, params, provider, null); } finally { + KeyUtil.destroySecretKeys(sk); KeyUtil.clear(passwd, encoding); } } /** * Creates an {@code EncryptedPrivateKeyInfo} by encrypting the specified - * {@code DEREncodable}. A valid password must be specified. A default + * {@code BinaryEncodable}. A valid password must be specified. A default * password-based encryption (PBE) algorithm and provider are used. * - * @param de the {@code DEREncodable} to encrypt. Supported types include + * @param be the {@code BinaryEncodable} to encrypt. Supported types include * {@code PrivateKey}, {@code KeyPair}, and {@code PKCS8EncodedKeySpec}. * @param password the password used for PBE encryption. This array is cloned * before use. * @return an {@code EncryptedPrivateKeyInfo} - * @throws NullPointerException if {@code de} or {@code password} is {@code null} - * @throws IllegalArgumentException if {@code de} is an unsupported - * {@code DEREncodable}, if an error occurs while generating the - * PBE key, or if the default algorithm is misconfigured + * @throws NullPointerException if {@code be} or {@code password} is {@code null} + * @throws IllegalArgumentException if {@code be} is an unsupported + * {@code BinaryEncodable} or has no encoding + * @throws CryptoException if an error occurs while generating the + * PBE key, if the default algorithm is misconfigured, or if an + * error occurs during encryption * * @implNote The {@code jdk.epkcs8.defaultAlgorithm} security property * defines the default encryption algorithm. The {@code AlgorithmParameterSpec} * defaults are determined by the provider. * - * @since 26 + * @since 27 */ @PreviewFeature(feature = PreviewFeature.Feature.PEM_API) - public static EncryptedPrivateKeyInfo encrypt(DEREncodable de, + public static EncryptedPrivateKeyInfo encrypt(BinaryEncodable be, char[] password) { - return encrypt(de, password, Pem.DEFAULT_ALGO, null, + return encrypt(be, password, Pem.DEFAULT_ALGO, null, null); } /** * Creates an {@code EncryptedPrivateKeyInfo} by encrypting the specified - * {@code DEREncodable}. A valid encryption algorithm and {@code Key} must + * {@code BinaryEncodable}. A valid encryption algorithm and {@code Key} must * be specified. * *
The format of the algorithm string is described in the
@@ -423,36 +430,37 @@ public non-sealed class EncryptedPrivateKeyInfo implements DEREncodable {
* Cipher Algorithms section of the Java Security Standard Algorithm Names
* Specification.
*
- * @param de the {@code DEREncodable} to encrypt. Supported types include
+ * @param be the {@code BinaryEncodable} to encrypt. Supported types include
* {@code PrivateKey}, {@code KeyPair}, and {@code PKCS8EncodedKeySpec}.
* @param encryptKey the key used to encrypt the encoding
* @param algorithm the encryption algorithm, such as a password-based
* encryption (PBE) algorithm
* @param params the {@code AlgorithmParameterSpec} used for encryption. If
* {@code null}, the provider’s default parameters are applied.
- * @param random the {@code SecureRandom} instance used during encryption.
- * If {@code null}, the default is used.
* @param provider the {@code Provider} for {@code Cipher} operations.
* If {@code null}, the default provider list is used.
+ * @param random the {@code SecureRandom} instance used during encryption.
+ * If {@code null}, the default is used.
* @return an {@code EncryptedPrivateKeyInfo}
- * @throws NullPointerException if {@code de}, {@code encryptKey}, or
+ * @throws NullPointerException if {@code be}, {@code encryptKey}, or
* {@code algorithm} is {@code null}
- * @throws IllegalArgumentException if {@code de} is an unsupported
- * {@code DEREncodable}, if {@code encryptKey} is invalid, if
+ * @throws IllegalArgumentException if {@code be} is an unsupported
+ * {@code BinaryEncodable} or has no encoding
+ * @throws CryptoException if {@code encryptKey} is invalid, if
* {@code algorithm} or {@code params} are not supported by any
* provider, or if an error occurs during encryption
*
- * @since 26
+ * @since 27
*/
@PreviewFeature(feature = PreviewFeature.Feature.PEM_API)
- public static EncryptedPrivateKeyInfo encrypt(DEREncodable de,
+ public static EncryptedPrivateKeyInfo encrypt(BinaryEncodable be,
Key encryptKey, String algorithm, AlgorithmParameterSpec params,
Provider provider, SecureRandom random) {
- Objects.requireNonNull(de, "a key must be specified.");
- Objects.requireNonNull(encryptKey, "an encryption key must be specified.");
- Objects.requireNonNull(algorithm, "an algorithm must be specified.");
- return encryptImpl(getEncoding(de), algorithm, encryptKey,
+ Objects.requireNonNull(be, "a key must be specified");
+ Objects.requireNonNull(encryptKey, "an encryption key must be specified");
+ Objects.requireNonNull(algorithm, "an algorithm must be specified");
+ return encryptImpl(getEncoding(be), algorithm, encryptKey,
params, provider, random);
}
@@ -489,7 +497,7 @@ public non-sealed class EncryptedPrivateKeyInfo implements DEREncodable {
} catch (InvalidAlgorithmParameterException | NoSuchAlgorithmException |
IllegalStateException | NoSuchPaddingException |
IllegalBlockSizeException | InvalidKeyException e) {
- throw new IllegalArgumentException(e);
+ throw new CryptoException(e);
} catch (BadPaddingException e) {
throw new AssertionError(e);
} finally {
@@ -517,39 +525,39 @@ public non-sealed class EncryptedPrivateKeyInfo implements DEREncodable {
@PreviewFeature(feature = PreviewFeature.Feature.PEM_API)
public PrivateKey getKey(char[] password)
throws NoSuchAlgorithmException, InvalidKeyException {
- Objects.requireNonNull(password, "a password must be specified.");
+ Objects.requireNonNull(password, "a password must be specified");
PBEKeySpec keySpec = new PBEKeySpec(password);
+ byte[] encoding = null;
try {
- return PKCS8Key.parseKey(Pem.decryptEncoding(this, keySpec), null);
+ encoding = Pem.decryptEncoding(this, keySpec);
+ return PKCS8Key.parseKey(encoding, null);
} finally {
keySpec.clearPassword();
+ KeyUtil.clear(encoding);
}
}
/**
* Extracts and returns the enclosed {@code PrivateKey} using the specified
- * decryption key and provider.
+ * decryption key.
*
- * @param decryptKey the decryption key. Must not be {@code null}.
- * @param provider the {@code Provider} for {@code Cipher} decryption
- * and {@code PrivateKey} generation. If {@code null}, the
- * default provider configuration is used.
+ * @param decryptKey the decryption key; must not be {@code null}
* @return the decrypted {@code PrivateKey}
* @throws NullPointerException if {@code decryptKey} is {@code null}
* @throws NoSuchAlgorithmException if the decryption algorithm is unsupported
* @throws InvalidKeyException if an error occurs during parsing,
* decryption, or key generation
*
- * @since 25
+ * @since 27
*/
@PreviewFeature(feature = PreviewFeature.Feature.PEM_API)
- public PrivateKey getKey(Key decryptKey, Provider provider)
+ public PrivateKey getKey(Key decryptKey)
throws NoSuchAlgorithmException, InvalidKeyException {
- Objects.requireNonNull(decryptKey,"a decryptKey must be specified.");
+ Objects.requireNonNull(decryptKey,"a decryptKey must be specified");
byte[] encoding = null;
try {
- encoding = decryptData(decryptKey, provider);
- return PKCS8Key.parseKey(encoding, provider);
+ encoding = decryptData(decryptKey, null);
+ return PKCS8Key.parseKey(encoding, null);
} finally {
KeyUtil.clear(encoding);
}
@@ -573,19 +581,22 @@ public non-sealed class EncryptedPrivateKeyInfo implements DEREncodable {
@PreviewFeature(feature = PreviewFeature.Feature.PEM_API)
public KeyPair getKeyPair(char[] password)
throws NoSuchAlgorithmException, InvalidKeyException {
- Objects.requireNonNull(password, "a password must be specified.");
+ Objects.requireNonNull(password, "a password must be specified");
PBEKeySpec keySpec = new PBEKeySpec(password);
- DEREncodable d;
+ BinaryEncodable d;
+ byte[] encoding = null;
try {
- d = Pem.toDEREncodable(Pem.decryptEncoding(this, keySpec), true, null);
+ encoding = Pem.decryptEncoding(this, keySpec);
+ d = Pem.toPKCS8Encodable(encoding, null);
} finally {
keySpec.clearPassword();
+ KeyUtil.clear(encoding);
}
return switch (d) {
case KeyPair kp -> kp;
case PrivateKey ignored -> throw new InvalidKeyException(
- "This encoding does not contain a public key.");
+ "This encoding does not contain a public key");
default -> throw new InvalidKeyException(
"Invalid class returned " + d.getClass().getName());
};
@@ -593,49 +604,52 @@ public non-sealed class EncryptedPrivateKeyInfo implements DEREncodable {
/**
* Extracts and returns the enclosed {@code KeyPair} using the specified
- * decryption key and provider. If the encoded data does not contain both a
+ * decryption key. If the encoded data does not contain both a
* public and private key, an {@code InvalidKeyException} is thrown.
*
- * @param decryptKey the decryption key. Must not be {@code null}.
- * @param provider the {@code Provider} for {@code Cipher} decryption
- * and key generation. If {@code null}, the default provider
- * configuration is used.
+ * @param decryptKey the decryption key; must not be {@code null}
* @return a decrypted {@code KeyPair}
* @throws NullPointerException if {@code decryptKey} is {@code null}
* @throws NoSuchAlgorithmException if the decryption algorithm is unsupported
* @throws InvalidKeyException if the encoded data lacks a public key, or if
* an error occurs during parsing, decryption, or key generation
*
- * @since 26
+ * @since 27
*/
@PreviewFeature(feature = PreviewFeature.Feature.PEM_API)
- public KeyPair getKeyPair(Key decryptKey, Provider provider)
+ public KeyPair getKeyPair(Key decryptKey)
throws NoSuchAlgorithmException, InvalidKeyException {
- Objects.requireNonNull(decryptKey,"a decryptKey must be specified.");
+ Objects.requireNonNull(decryptKey,"a decryptKey must be specified");
- DEREncodable d = Pem.toDEREncodable(
- decryptData(decryptKey, provider),true, provider);
+ BinaryEncodable d;
+ byte[] encoding = null;
+ try {
+ encoding = decryptData(decryptKey, null);
+ d = Pem.toPKCS8Encodable(encoding, null);
+ } finally {
+ KeyUtil.clear(encoding);
+ }
return switch (d) {
case KeyPair kp -> kp;
case PrivateKey ignored -> throw new InvalidKeyException(
- "This encoding does not contain a public key.");
+ "This encoding does not contain a public key");
default -> throw new InvalidKeyException(
"Invalid class returned " + d.getClass().getName());
};
}
/**
- * Extract the enclosed PKCS8EncodedKeySpec object from the
- * encrypted data and return it.
+ * Extracts the enclosed PKCS8EncodedKeySpec object from the
+ * encrypted data and returns it.
* @param decryptKey key used for decrypting the encrypted data.
- * @return the PKCS8EncodedKeySpec object.
+ * @return the PKCS8EncodedKeySpec object with a specified algorithm
* @exception NullPointerException if {@code decryptKey}
* is {@code null}.
* @exception NoSuchAlgorithmException if cannot find appropriate
* cipher to decrypt the encrypted data.
* @exception InvalidKeyException if {@code decryptKey}
* cannot be used to decrypt the encrypted data or the decryption
- * result is not a valid PKCS8KeySpec.
+ * result is not a valid PKCS8EncodedKeySpec.
*
* @since 1.5
*/
@@ -648,12 +662,12 @@ public non-sealed class EncryptedPrivateKeyInfo implements DEREncodable {
}
/**
- * Extract the enclosed PKCS8EncodedKeySpec object from the
- * encrypted data and return it.
+ * Extracts the enclosed PKCS8EncodedKeySpec object from the
+ * encrypted data and returns it.
* @param decryptKey key used for decrypting the encrypted data.
* @param providerName the name of provider whose cipher
* implementation will be used.
- * @return the PKCS8EncodedKeySpec object
+ * @return the PKCS8EncodedKeySpec object with a specified algorithm
* @exception NullPointerException if {@code decryptKey}
* or {@code providerName} is {@code null}.
* @exception NoSuchProviderException if no provider
@@ -662,7 +676,7 @@ public non-sealed class EncryptedPrivateKeyInfo implements DEREncodable {
* cipher to decrypt the encrypted data.
* @exception InvalidKeyException if {@code decryptKey}
* cannot be used to decrypt the encrypted data or the decryption
- * result is not a valid PKCS8KeySpec.
+ * result is not a valid PKCS8EncodedKeySpec.
*
* @since 1.5
*/
@@ -670,7 +684,7 @@ public non-sealed class EncryptedPrivateKeyInfo implements DEREncodable {
String providerName) throws NoSuchProviderException,
NoSuchAlgorithmException, InvalidKeyException {
Objects.requireNonNull(decryptKey, "decryptKey is null");
- Objects.requireNonNull(providerName, "provider is null");
+ Objects.requireNonNull(providerName, "providerName is null");
Provider provider = Security.getProvider(providerName);
if (provider == null) {
throw new NoSuchProviderException("provider " +
@@ -680,19 +694,18 @@ public non-sealed class EncryptedPrivateKeyInfo implements DEREncodable {
}
/**
- * Extract the enclosed PKCS8EncodedKeySpec object from the
- * encrypted data and return it.
+ * Extracts the enclosed PKCS8EncodedKeySpec object from the
+ * encrypted data and returns it.
* @param decryptKey key used for decrypting the encrypted data.
- * @param provider the name of provider whose cipher implementation
- * will be used.
- * @return the PKCS8EncodedKeySpec object.
+ * @param provider the provider whose cipher implementation will be used.
+ * @return the PKCS8EncodedKeySpec object with a specified algorithm
* @exception NullPointerException if {@code decryptKey}
* or {@code provider} is {@code null}.
* @exception NoSuchAlgorithmException if cannot find appropriate
* cipher to decrypt the encrypted data in {@code provider}.
* @exception InvalidKeyException if {@code decryptKey}
* cannot be used to decrypt the encrypted data or the decryption
- * result is not a valid PKCS8KeySpec.
+ * result is not a valid PKCS8EncodedKeySpec.
*
* @since 1.5
*/
@@ -745,22 +758,26 @@ public non-sealed class EncryptedPrivateKeyInfo implements DEREncodable {
KeyUtil.getAlgorithm(encodedKey));
}
- // Return the PKCS#8 encoding from a DEREncodable
- private static byte[] getEncoding(DEREncodable d) {
- return switch (d) {
- case PrivateKey p -> p.getEncoded();
- case PKCS8EncodedKeySpec p8 -> p8.getEncoded();
- case KeyPair kp -> {
- try {
- yield PKCS8Key.getEncoded(kp.getPublic().getEncoded(),
- kp.getPrivate().getEncoded());
- } catch (IOException e) {
- throw new IllegalArgumentException(e);
+ // Return the PKCS#8 encoding from a BinaryEncodable
+ private static byte[] getEncoding(BinaryEncodable d) {
+ try {
+ return switch (d) {
+ case PrivateKey p -> p.getEncoded();
+ case PKCS8EncodedKeySpec p8 -> p8.getEncoded();
+ case KeyPair kp -> {
+ try {
+ yield PKCS8Key.getEncoded(kp.getPublic().getEncoded(),
+ kp.getPrivate().getEncoded());
+ } catch (IOException e) {
+ throw new IllegalArgumentException(e);
+ }
}
- }
- default -> throw new IllegalArgumentException(
- d.getClass().getName() + " not supported by this method");
- };
+ default -> throw new IllegalArgumentException(
+ d.getClass().getName() + " not supported by this method");
+ };
+ } catch (NullPointerException e) {
+ throw new IllegalArgumentException(e);
+ }
}
// Generate a SecretKey from the password.
@@ -777,7 +794,7 @@ public non-sealed class EncryptedPrivateKeyInfo implements DEREncodable {
}
return factory.generateSecret(keySpec);
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
- throw new IllegalArgumentException(e);
+ throw new CryptoException(e);
} finally {
keySpec.clearPassword();
}
diff --git a/src/java.base/share/classes/jdk/internal/javac/PreviewFeature.java b/src/java.base/share/classes/jdk/internal/javac/PreviewFeature.java
index 82c55d6f017..064e4e1fd92 100644
--- a/src/java.base/share/classes/jdk/internal/javac/PreviewFeature.java
+++ b/src/java.base/share/classes/jdk/internal/javac/PreviewFeature.java
@@ -68,8 +68,8 @@ public @interface PreviewFeature {
STRUCTURED_CONCURRENCY,
@JEP(number = 531, title = "Lazy Constants", status = "Third Preview")
LAZY_CONSTANTS,
- @JEP(number=524, title="PEM Encodings of Cryptographic Objects",
- status="Second Preview")
+ @JEP(number=538, title="PEM Encodings of Cryptographic Objects",
+ status="Third Preview")
PEM_API,
LANGUAGE_MODEL,
/**
diff --git a/src/java.base/share/classes/sun/security/provider/X509Factory.java b/src/java.base/share/classes/sun/security/provider/X509Factory.java
index 154d1428414..9a4f3717065 100644
--- a/src/java.base/share/classes/sun/security/provider/X509Factory.java
+++ b/src/java.base/share/classes/sun/security/provider/X509Factory.java
@@ -581,7 +581,7 @@ public class X509Factory extends CertificateFactorySpi {
} catch (EOFException e) {
return null;
}
- return Base64.getDecoder().decode(rec.content());
+ return Base64.getMimeDecoder().decode(rec.content());
} catch (IllegalArgumentException e) {
throw new IOException(e);
}
diff --git a/src/java.base/share/classes/sun/security/util/KeyUtil.java b/src/java.base/share/classes/sun/security/util/KeyUtil.java
index e9dabdc5b06..5a14deb70a4 100644
--- a/src/java.base/share/classes/sun/security/util/KeyUtil.java
+++ b/src/java.base/share/classes/sun/security/util/KeyUtil.java
@@ -31,6 +31,7 @@ import java.security.*;
import java.security.interfaces.*;
import java.security.spec.*;
import java.util.Arrays;
+import java.util.function.Function;
import javax.crypto.SecretKey;
import javax.crypto.interfaces.DHKey;
import javax.crypto.interfaces.DHPublicKey;
@@ -568,5 +569,25 @@ public final class KeyUtil {
}
}
}
+
+ /**
+ * Executes {@code op} with {@code encoding} and then zeroes {@code encoding}
+ * in a {@code finally} block before returning or propagating an exception.
+ *
+ * {@code encoding} is temporary sensitive data and is always wiped.
+ *
+ * Usage constraint: {@code op} must not return {@code encoding} itself, or
+ * any value backed by the same array. Otherwise, the returned data will already
+ * be zeroed when this method returns.
+ */
+ public static