From dc4bb5acbe5d766f5fe389d8aeac0cc649f4f776 Mon Sep 17 00:00:00 2001 From: Anthony Scarpino Date: Wed, 10 Jun 2026 17:35:43 +0000 Subject: [PATCH] 8383608: Make BinaryEncodable non-exhaustive Reviewed-by: mullan --- .../java/security/BinaryEncodable.java | 34 ++++++++-- .../classes/java/security/PEMDecoder.java | 25 ++++--- .../internal/InternalBinaryEncodable.java | 39 +++++++++++ test/jdk/sun/security/internal/CheckIBE.java | 67 +++++++++++++++++++ .../sun/security/internal/ExhaustiveBE.java | 65 ++++++++++++++++++ 5 files changed, 212 insertions(+), 18 deletions(-) create mode 100644 src/java.base/share/classes/sun/security/internal/InternalBinaryEncodable.java create mode 100644 test/jdk/sun/security/internal/CheckIBE.java create mode 100644 test/jdk/sun/security/internal/ExhaustiveBE.java diff --git a/src/java.base/share/classes/java/security/BinaryEncodable.java b/src/java.base/share/classes/java/security/BinaryEncodable.java index bd5d05ee4ec..a1713c413ba 100644 --- a/src/java.base/share/classes/java/security/BinaryEncodable.java +++ b/src/java.base/share/classes/java/security/BinaryEncodable.java @@ -32,15 +32,35 @@ import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; import jdk.internal.javac.PreviewFeature; +import sun.security.internal.InternalBinaryEncodable; + /** - * This interface is implemented by security API classes that contain - * binary-encodable cryptographic material. + * This interface identifies the cryptographic objects that can be converted + * to and from binary data, and thereby encoded and decoded as PEM text. * - *

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}. + *

The APIs for cryptographic objects such as public keys, private keys, + * certificates, and certificate revocation lists all provide the means to + * convert their instances to and from standardized binary representations. + * Other kinds of cryptographic objects, such as certificate requests, have + * no corresponding API but can still be expressed as standardized binary + * representations. The {@code BinaryEncodable} interface allows the + * {@link PEMEncoder} and {@link PEMDecoder} classes to operate uniformly on + * binary representations of key or certificate material. + * + *

The permitted subtype {@code PEM} is notable for supporting the encoding + * and decoding of PEM text that represents cryptographic objects for which no + * API exists. In future releases, other permitted subtypes may be added to + * support the encoding and decoding of such cryptographic objects. + * + *

The list of permitted subtypes shown after {@code permits} is not + * exhaustive. This means if application code switches over a + * {@code BinaryEncodable} value, the {@code switch} cannot be made exhaustive + * simply by providing a {@code case} label for every permitted subtype shown + * in the list; there also must be a {@code default} or + * {@code case BinaryEncodable} label to handle additional subtypes. This + * allows the list of permitted subtypes to change over time without causing + * pre-existing switches to fail because of an unrecognized subtype. * * @see AsymmetricKey * @see KeyPair @@ -57,5 +77,5 @@ import jdk.internal.javac.PreviewFeature; @PreviewFeature(feature = PreviewFeature.Feature.PEM_API) public sealed interface BinaryEncodable permits AsymmetricKey, KeyPair, PKCS8EncodedKeySpec, X509EncodedKeySpec, EncryptedPrivateKeyInfo, - X509Certificate, X509CRL, PEM { + X509Certificate, X509CRL, PEM, InternalBinaryEncodable { } diff --git a/src/java.base/share/classes/java/security/PEMDecoder.java b/src/java.base/share/classes/java/security/PEMDecoder.java index f5a6a70d0f5..8ebc83f93d1 100644 --- a/src/java.base/share/classes/java/security/PEMDecoder.java +++ b/src/java.base/share/classes/java/security/PEMDecoder.java @@ -48,10 +48,10 @@ import java.util.Objects; * 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 - * Base64-encoded content enclosed by a type-identifying header - * and footer. + * Base64-encoded content enclosed by a header and footer that identify the + * type of the content. * - *

The {@link #decode(String)} and {@link #decode(InputStream)} methods + *

The {@link #decode(String)} and {@link #decode(InputStream)} methods * return an instance of a class that matches the PEM type and implements * {@link BinaryEncodable}, as follows: *

* *

If the PEM type has no corresponding class, {@code decode(String)} and - * {@code decode(InputStream)} will return a {@code PEM} object. + * {@code decode(InputStream)} return a {@code PEM} object. + * + *

If application code switches over the {@code BinaryEncodable} result of + * {@link #decode(String)} or {@link #decode(InputStream)}, the {@code switch} cannot + * be made exhaustive simply by providing a {@code case} label for every permitted + * subtype listed for {@code BinaryEncodable}; there also must be a {@code default} + * or {@code case BinaryEncodable} label to handle additional subtypes that + * might be added in the future. * *

The {@link #decode(String, Class)} and {@link #decode(InputStream, Class)} - * methods accept a class parameter specifying the desired {@code BinaryEncodable} - * type. These methods avoid the need for casting and are useful when multiple + * methods accept a parameter specifying the desired {@code BinaryEncodable} + * result. 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 @@ -109,11 +116,6 @@ import java.util.Objects; * 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. * *

Example: decode a private key: @@ -136,6 +138,7 @@ import java.util.Objects; * @see PEMEncoder * @see PEM * @see EncryptedPrivateKeyInfo + * @see BinaryEncodable * * @spec https://www.rfc-editor.org/info/rfc1421 * RFC 1421: Privacy Enhancement for Internet Electronic Mail diff --git a/src/java.base/share/classes/sun/security/internal/InternalBinaryEncodable.java b/src/java.base/share/classes/sun/security/internal/InternalBinaryEncodable.java new file mode 100644 index 00000000000..72d0008a505 --- /dev/null +++ b/src/java.base/share/classes/sun/security/internal/InternalBinaryEncodable.java @@ -0,0 +1,39 @@ +/* + * 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 sun.security.internal; + +import java.security.BinaryEncodable; + +/** + * This class is a non-public subtype of BinaryEncodable. This type + * allows the BinaryEncodable list of permitted subtypes to change + * over time without causing pre-existing switches to fail because of an + * unrecognized subtype. + */ + +public final class InternalBinaryEncodable implements BinaryEncodable { + private InternalBinaryEncodable() {} +} diff --git a/test/jdk/sun/security/internal/CheckIBE.java b/test/jdk/sun/security/internal/CheckIBE.java new file mode 100644 index 00000000000..802cd336ebc --- /dev/null +++ b/test/jdk/sun/security/internal/CheckIBE.java @@ -0,0 +1,67 @@ +/* + * 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. + * + * 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 8383608 + * @summary check that InternalBinaryEncodable exists + * @enablePreview + * @modules java.base/sun.security.internal + * @run main CheckIBE + */ + +import javax.crypto.EncryptedPrivateKeyInfo; +import java.security.AsymmetricKey; +import java.security.BinaryEncodable; +import java.security.KeyPair; +import java.security.PEM; +import java.security.cert.X509CRL; +import java.security.cert.X509Certificate; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; + +import sun.security.internal.InternalBinaryEncodable; + +/* + * This test verifies that BinaryEncodable has the expected set of permitted + * subtypes, including InternalBinaryEncodable. If this switch stops compiling, + * update the cases to match the BinaryEncodable permits list. + */ + +public class CheckIBE { + public static void main(String[] args) { + BinaryEncodable be = new PEM("TEST", "TEST"); + + switch (be) { + case AsymmetricKey ignored -> {} + case KeyPair ignored -> {} + case PKCS8EncodedKeySpec ignored -> {} + case X509EncodedKeySpec ignored -> {} + case EncryptedPrivateKeyInfo ignored -> {} + case X509Certificate ignored -> {} + case X509CRL ignored -> {} + case PEM ignored -> {} + case InternalBinaryEncodable ignored -> {} + } + } +} diff --git a/test/jdk/sun/security/internal/ExhaustiveBE.java b/test/jdk/sun/security/internal/ExhaustiveBE.java new file mode 100644 index 00000000000..37222a0310a --- /dev/null +++ b/test/jdk/sun/security/internal/ExhaustiveBE.java @@ -0,0 +1,65 @@ +/* + * 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. + * + * 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 8383608 + * @summary verify switches over BinaryEncodable are not exhaustive + * @enablePreview + * @compile/fail ExhaustiveBE.java + */ + +import javax.crypto.EncryptedPrivateKeyInfo; +import java.security.AsymmetricKey; +import java.security.BinaryEncodable; +import java.security.KeyPair; +import java.security.PEM; +import java.security.cert.X509CRL; +import java.security.cert.X509Certificate; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; + +/* + * This test verifies that application code cannot exhaustively switch over + * BinaryEncodable by naming only the public permitted subtypes. Compilation + * must fail because application code needs a default case, or a + * BinaryEncodable case, to cover the internal permitted subtype + * InternalBinaryEncodable. + */ + +public class ExhaustiveBE { + public static void main(String[] args) { + BinaryEncodable be = new PEM("TEST", "TEST"); + + switch (be) { + case AsymmetricKey ignored -> {} + case KeyPair ignored -> {} + case PKCS8EncodedKeySpec ignored -> {} + case X509EncodedKeySpec ignored -> {} + case EncryptedPrivateKeyInfo ignored -> {} + case X509Certificate ignored -> {} + case X509CRL ignored -> {} + case PEM ignored -> {} + } + } +}