mirror of
https://github.com/openjdk/jdk.git
synced 2026-06-06 10:42:45 +00:00
8377506: Implement JEP 538: PEM Encodings of Cryptographic Objects (Third Preview)
Reviewed-by: weijun, mullan
This commit is contained in:
parent
79923019f5
commit
e70e691307
@ -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
|
||||
|
||||
@ -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.
|
||||
*
|
||||
* <p> 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 {
|
||||
}
|
||||
@ -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;
|
||||
|
||||
@ -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.
|
||||
*
|
||||
* <p> 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.
|
||||
*
|
||||
* <p> 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}.
|
||||
*
|
||||
* <p> When constructing a {@code PEM} instance, both {@code type} and
|
||||
* {@code content} must not be {@code null}.
|
||||
*
|
||||
* <p>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}.
|
||||
|
||||
* <p> Common {@code type} values include, but are not limited to:
|
||||
* <p>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.
|
||||
*
|
||||
* <p> {@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.
|
||||
* <p>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}.
|
||||
* <p>A {@code PEM} object can be encoded to its textual representation by
|
||||
* invoking {@link #toString()} or by using {@link PEMEncoder}.
|
||||
*
|
||||
* <p>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}.
|
||||
*
|
||||
* <p>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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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.
|
||||
*
|
||||
* <p>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:
|
||||
* <ul>
|
||||
* <li>CERTIFICATE : {@link X509Certificate}</li>
|
||||
* <li>X509 CRL : {@link X509CRL}</li>
|
||||
* <li>PUBLIC KEY : {@link PublicKey}</li>
|
||||
* <li>PRIVATE KEY : {@link PrivateKey} or {@link KeyPair}
|
||||
* <li>CERTIFICATE: {@link X509Certificate}</li>
|
||||
* <li>X509 CRL: {@link X509CRL}</li>
|
||||
* <li>PUBLIC KEY: {@link PublicKey}</li>
|
||||
* <li>PRIVATE KEY: {@link PrivateKey} or {@link KeyPair}
|
||||
* (if the encoding contains a public key)</li>
|
||||
* <li>ENCRYPTED PRIVATE KEY : {@link EncryptedPrivateKeyInfo}</li>
|
||||
* <li>Other types : {@link PEM}</li>
|
||||
* <li>ENCRYPTED PRIVATE KEY: {@link EncryptedPrivateKeyInfo}</li>
|
||||
* <li>Other types: {@link PEM}</li>
|
||||
* </ul>
|
||||
* When used with a {@code PEMDecoder} instance configured for decryption:
|
||||
* <ul>
|
||||
* <li>ENCRYPTED PRIVATE KEY : {@link PrivateKey} or {@link KeyPair}
|
||||
* <li>ENCRYPTED PRIVATE KEY: {@link PrivateKey} or {@link KeyPair}
|
||||
* (if the encoding contains a public key)</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p> 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.
|
||||
*
|
||||
* <p> If the PEM type does not have a corresponding class,
|
||||
* {@code decode(String)} and {@code decode(InputStream)} will return a
|
||||
* {@code PEM} object.
|
||||
* <p> If the PEM type has no corresponding class, {@code decode(String)} and
|
||||
* {@code decode(InputStream)} will return a {@code PEM} object.
|
||||
*
|
||||
* <p> 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.
|
||||
*
|
||||
* <p> 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:
|
||||
* <ul>
|
||||
* <li>PUBLIC KEY : {@link X509EncodedKeySpec}</li>
|
||||
* <li>PRIVATE KEY : {@link PKCS8EncodedKeySpec}</li>
|
||||
* <li>PRIVATE KEY : {@link PublicKey} (if the encoding contains a public key)</li>
|
||||
* <li>PRIVATE KEY : {@link X509EncodedKeySpec} (if the encoding contains a public key)</li>
|
||||
* <li>PUBLIC KEY: {@link X509EncodedKeySpec}</li>
|
||||
* <li>PRIVATE KEY: {@link PKCS8EncodedKeySpec}</li>
|
||||
* <li>PRIVATE KEY: {@link PublicKey} (if the encoding contains a public key)</li>
|
||||
* <li>PRIVATE KEY: {@link X509EncodedKeySpec} (if the encoding contains a public key)</li>
|
||||
* </ul>
|
||||
* When used with a {@code PEMDecoder} instance configured for decryption:
|
||||
* <ul>
|
||||
* <li>ENCRYPTED PRIVATE KEY : {@link PKCS8EncodedKeySpec}</li>
|
||||
* <li>ENCRYPTED PRIVATE KEY : {@link PublicKey} (if the encoding contains a public key)</li>
|
||||
* <li>ENCRYPTED PRIVATE KEY : {@link X509EncodedKeySpec} (if the encoding contains a public key)</li>
|
||||
* <li>ENCRYPTED PRIVATE KEY: {@link PKCS8EncodedKeySpec}</li>
|
||||
* <li>ENCRYPTED PRIVATE KEY: {@link PublicKey} (if the encoding contains a public key)</li>
|
||||
* <li>ENCRYPTED PRIVATE KEY: {@link X509EncodedKeySpec} (if the encoding contains a public key)</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p> 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.
|
||||
*
|
||||
* <p> 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}.
|
||||
*
|
||||
* <p> This class is immutable and thread-safe.
|
||||
*
|
||||
@ -127,14 +125,13 @@ import java.util.Objects;
|
||||
* <p> 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}.
|
||||
*
|
||||
* <p> This method reads the {@code String} until PEM data is found
|
||||
* or the end of the {@code String} is reached. If no PEM data is found,
|
||||
* or the end of the {@code String} is reached. If no PEM data is found,
|
||||
* an {@code IllegalArgumentException} is thrown.
|
||||
*
|
||||
* <p> A {@code DEREncodable} will be returned that best represents the
|
||||
* decoded data. If the PEM type is not supported, a {@code PEM} object is
|
||||
* <p> 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.
|
||||
*
|
||||
* <p> Input consumed by this method is read in as
|
||||
* <p> 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}.
|
||||
*
|
||||
* <p> 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.
|
||||
*
|
||||
* <p> A {@code DEREncodable} will be returned that best represents the
|
||||
* decoded data. If the PEM type is not supported, a {@code PEM} object is
|
||||
* <p> 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.
|
||||
*
|
||||
* <p> 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.
|
||||
*
|
||||
* <p> This method reads the {@code String} until PEM data is found
|
||||
* or the end of the {@code String} is reached. If no PEM data is found,
|
||||
* an {@code IllegalArgumentException} is thrown.
|
||||
*
|
||||
* <p> If the class parameter is {@code 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.
|
||||
*
|
||||
* <p> Input consumed by this method is read in as
|
||||
* {@link java.nio.charset.StandardCharsets#UTF_8 UTF-8}.
|
||||
*
|
||||
* @param <S> class type parameter that extends {@code DEREncodable}
|
||||
* @param str the {@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 extends DEREncodable> S decode(String str, Class<S> 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.
|
||||
*
|
||||
* <p>{@code tClass} must be an appropriate class for the PEM type.
|
||||
*
|
||||
* <p>This method reads the {@code String} until PEM data is found or the end
|
||||
* of the {@code String} is reached. If no PEM data is found, an
|
||||
* {@code IllegalArgumentException} is thrown.
|
||||
*
|
||||
* <p>If {@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.
|
||||
*
|
||||
* <p>The input is interpreted as
|
||||
* {@link java.nio.charset.StandardCharsets#UTF_8 UTF-8}.
|
||||
*
|
||||
* @param <S> 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 extends BinaryEncodable> S decode(String str, Class<S> 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}.
|
||||
*
|
||||
* <p>{@code tClass} must be an appropriate class for the PEM type.
|
||||
*
|
||||
* <p> 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.
|
||||
*
|
||||
* <p> 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.
|
||||
* <p> 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.
|
||||
*
|
||||
* <p> If no PEM data is found, an {@code EOFException} is thrown.
|
||||
*
|
||||
* @param <S> class type parameter that extends {@code DEREncodable}
|
||||
* @param <S> 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 extends DEREncodable> S decode(InputStream is, Class<S> tClass)
|
||||
public <S extends BinaryEncodable> S decode(InputStream is, Class<S> 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.
|
||||
|
||||
@ -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.
|
||||
*
|
||||
* <p> 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}.
|
||||
*
|
||||
* <p> 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.
|
||||
*
|
||||
* <p> 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.
|
||||
*
|
||||
* <p> The following lists the supported {@code DEREncodable} classes and
|
||||
* the PEM types they encode as:
|
||||
* <p> The following lists the supported {@code BinaryEncodable} classes and
|
||||
* the PEM types they encode to:
|
||||
* <ul>
|
||||
* <li>{@link X509Certificate} : CERTIFICATE</li>
|
||||
* <li>{@link X509CRL} : X509 CRL</li>
|
||||
* <li>{@link PublicKey} : PUBLIC KEY</li>
|
||||
* <li>{@link PrivateKey} : PRIVATE KEY</li>
|
||||
* <li>{@link EncryptedPrivateKeyInfo} : ENCRYPTED PRIVATE KEY</li>
|
||||
* <li>{@link KeyPair} : PRIVATE KEY</li>
|
||||
* <li>{@link X509EncodedKeySpec} : PUBLIC KEY</li>
|
||||
* <li>{@link PKCS8EncodedKeySpec} : PRIVATE KEY</li>
|
||||
* <li>{@link PEM} : {@code PEM.type()}</li>
|
||||
* <li>{@link X509Certificate}: CERTIFICATE</li>
|
||||
* <li>{@link X509CRL}: X509 CRL</li>
|
||||
* <li>{@link PublicKey}: PUBLIC KEY</li>
|
||||
* <li>{@link PrivateKey}: PRIVATE KEY</li>
|
||||
* <li>{@link EncryptedPrivateKeyInfo}: ENCRYPTED PRIVATE KEY</li>
|
||||
* <li>{@link KeyPair}: PRIVATE KEY</li>
|
||||
* <li>{@link X509EncodedKeySpec}: PUBLIC KEY</li>
|
||||
* <li>{@link PKCS8EncodedKeySpec}: PRIVATE KEY</li>
|
||||
* <li>{@link PEM}: {@link PEM#type()}</li>
|
||||
* </ul>
|
||||
* <p> When used with a {@code PEMEncoder} instance configured for encryption:
|
||||
* <ul>
|
||||
* <li>{@link PrivateKey} : ENCRYPTED PRIVATE KEY</li>
|
||||
* <li>{@link KeyPair} : ENCRYPTED PRIVATE KEY</li>
|
||||
* <li>{@link PKCS8EncodedKeySpec} : ENCRYPTED PRIVATE KEY</li>
|
||||
* <li>{@link PrivateKey}: ENCRYPTED PRIVATE KEY</li>
|
||||
* <li>{@link KeyPair}: ENCRYPTED PRIVATE KEY</li>
|
||||
* <li>{@link PKCS8EncodedKeySpec}: ENCRYPTED PRIVATE KEY</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p> 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.
|
||||
*
|
||||
* <p> 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}.
|
||||
*
|
||||
* <p> 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) {
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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.
|
||||
*
|
||||
|
||||
@ -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.
|
||||
*
|
||||
|
||||
@ -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.
|
||||
*
|
||||
* <p>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.
|
||||
*
|
||||
* <p>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.
|
||||
*
|
||||
* <p> 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);
|
||||
}
|
||||
}
|
||||
@ -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.
|
||||
* <br>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.
|
||||
*
|
||||
* <p>The format of the PBE algorithm string is described in the
|
||||
@ -346,7 +348,7 @@ public non-sealed class EncryptedPrivateKeyInfo implements DEREncodable {
|
||||
* Cipher Algorithms</a> 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.
|
||||
*
|
||||
* <p>The format of the algorithm string is described in the
|
||||
@ -423,36 +430,37 @@ public non-sealed class EncryptedPrivateKeyInfo implements DEREncodable {
|
||||
* Cipher Algorithms</a> 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();
|
||||
}
|
||||
|
||||
@ -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,
|
||||
/**
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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 <T> T clear(byte[] encoding, Function<byte[], T> op) {
|
||||
try {
|
||||
return op.apply(encoding);
|
||||
} finally {
|
||||
if (encoding != null) {
|
||||
Arrays.fill(encoding, (byte)0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -29,6 +29,7 @@ import sun.security.pkcs.PKCS8Key;
|
||||
import sun.security.x509.AlgorithmId;
|
||||
|
||||
import javax.crypto.EncryptedPrivateKeyInfo;
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.SecretKeyFactory;
|
||||
import javax.crypto.spec.PBEKeySpec;
|
||||
import java.io.*;
|
||||
@ -47,7 +48,11 @@ import java.util.regex.Pattern;
|
||||
* A utility class for PEM format encoding.
|
||||
*/
|
||||
public class Pem {
|
||||
private static final byte[] CRLF = new byte[] {'\r', '\n'};
|
||||
private static final byte[] CRLF = new byte[]{'\r', '\n'};
|
||||
private static final byte[] DASH;
|
||||
private static final byte[] BEGIN_B;
|
||||
private static final byte[] BEGIN_PREFIX;
|
||||
private static final byte[] END_PREFIX;
|
||||
|
||||
// Default algorithm from jdk.epkcs8.defaultAlgorithm in java.security
|
||||
public static final String DEFAULT_ALGO;
|
||||
@ -62,10 +67,10 @@ public class Pem {
|
||||
private static final Pattern LINE_WRAP_64_PATTERN;
|
||||
|
||||
// Lazy initialized PBES2 OID value
|
||||
private static ObjectIdentifier PBES2OID;
|
||||
private static volatile ObjectIdentifier PBES2OID;
|
||||
|
||||
// Lazy initialized singleton encoder.
|
||||
private static Base64.Encoder b64Encoder;
|
||||
private static volatile Base64.Encoder b64Encoder;
|
||||
|
||||
static {
|
||||
String algo = Security.getProperty("jdk.epkcs8.defaultAlgorithm");
|
||||
@ -75,6 +80,10 @@ public class Pem {
|
||||
Pattern.CASE_INSENSITIVE);
|
||||
STRIP_WHITESPACE_PATTERN = Pattern.compile("\\s+");
|
||||
LINE_WRAP_64_PATTERN = Pattern.compile("(.{64})");
|
||||
DASH = "-----".getBytes(StandardCharsets.ISO_8859_1);
|
||||
BEGIN_B = "-----B".getBytes(StandardCharsets.ISO_8859_1);
|
||||
BEGIN_PREFIX = "-----BEGIN ".getBytes(StandardCharsets.ISO_8859_1);
|
||||
END_PREFIX = "-----END ".getBytes(StandardCharsets.ISO_8859_1);
|
||||
}
|
||||
|
||||
public static final String CERTIFICATE = "CERTIFICATE";
|
||||
@ -162,13 +171,10 @@ public class Pem {
|
||||
* @param shortHeader if true, the hyphen length is 4 because the first
|
||||
* hyphen is assumed to have been read. This is needed
|
||||
* for the CertificateFactory X509 implementation.
|
||||
* @return a new PEMRecord
|
||||
* @return a PEM instance
|
||||
* @throws IOException on IO errors or PEM syntax errors that leave
|
||||
* the read position not at the end of a PEM block
|
||||
* @throws EOFException when at the unexpected end of the stream
|
||||
* @throws IllegalArgumentException when a PEM syntax error occurs,
|
||||
* but the read position in the stream is at the end of the block, so
|
||||
* future reads can be successful.
|
||||
*/
|
||||
public static PEM readPEM(InputStream is, boolean shortHeader)
|
||||
throws IOException {
|
||||
@ -176,257 +182,268 @@ public class Pem {
|
||||
|
||||
int hyphen = (shortHeader ? 1 : 0);
|
||||
int eol = 0;
|
||||
ByteArrayOutputStream os = new ByteArrayOutputStream(6);
|
||||
var os = new ClearableBufferStream(6); // preData
|
||||
var readbuf = new ByteArrayOutputStream(64); // header/footer
|
||||
var pem = new ClearableBufferStream(1024); // PEM
|
||||
String headerType, footerType;
|
||||
byte[] encoding = null;
|
||||
|
||||
// Find 5 hyphens followed by a 'B' to start processing the header.
|
||||
boolean headerStarted = false;
|
||||
do {
|
||||
int d = is.read();
|
||||
switch (d) {
|
||||
case '-' -> hyphen++;
|
||||
case -1 -> {
|
||||
if (os.size() == 0) {
|
||||
throw new EOFException("No data available");
|
||||
try {
|
||||
// Find 5 hyphens followed by a 'B' to start processing the header.
|
||||
boolean headerStarted = false;
|
||||
do {
|
||||
int d = is.read();
|
||||
switch (d) {
|
||||
case '-' -> hyphen++;
|
||||
case -1 -> {
|
||||
if (os.size() == 0) {
|
||||
throw new EOFException("No data available");
|
||||
}
|
||||
throw new EOFException("No PEM data found");
|
||||
}
|
||||
throw new EOFException("No PEM data found");
|
||||
}
|
||||
case 'B' -> {
|
||||
if (hyphen == 5) {
|
||||
headerStarted = true;
|
||||
} else {
|
||||
hyphen = 0;
|
||||
case 'B' -> {
|
||||
if (hyphen == 5) {
|
||||
headerStarted = true;
|
||||
} else {
|
||||
hyphen = 0;
|
||||
}
|
||||
}
|
||||
default -> hyphen = 0;
|
||||
}
|
||||
default -> hyphen = 0;
|
||||
}
|
||||
os.write(d);
|
||||
} while (!headerStarted);
|
||||
os.write(d);
|
||||
} while (!headerStarted);
|
||||
|
||||
StringBuilder sb = new StringBuilder(64);
|
||||
sb.append("-----B");
|
||||
hyphen = 0;
|
||||
int c;
|
||||
readbuf.writeBytes(BEGIN_B);
|
||||
hyphen = 0;
|
||||
int c;
|
||||
|
||||
// Get header definition until first hyphen
|
||||
do {
|
||||
switch (c = is.read()) {
|
||||
case '-' -> hyphen++;
|
||||
case -1 -> throw new EOFException("Input ended prematurely");
|
||||
case '\n', '\r' -> throw new IOException("Incomplete header");
|
||||
default -> sb.append((char) c);
|
||||
}
|
||||
} while (hyphen == 0);
|
||||
// Get header definition until first hyphen
|
||||
do {
|
||||
switch (c = is.read()) {
|
||||
case '-' -> hyphen++;
|
||||
case -1 -> throw new EOFException("Input ended prematurely");
|
||||
case '\n', '\r' -> throw new IOException("Incomplete header");
|
||||
default -> readbuf.write(c);
|
||||
}
|
||||
} while (hyphen == 0);
|
||||
|
||||
// Verify header ending with 5 hyphens.
|
||||
do {
|
||||
switch (is.read()) {
|
||||
case '-' -> hyphen++;
|
||||
default ->
|
||||
// Verify header ending with 5 hyphens.
|
||||
do {
|
||||
if (is.read() == '-') {
|
||||
hyphen++;
|
||||
} else {
|
||||
throw new IOException("Incomplete header");
|
||||
}
|
||||
} while (hyphen < 5);
|
||||
|
||||
readbuf.writeBytes(DASH);
|
||||
byte[] header = readbuf.toByteArray();
|
||||
if (header.length < 16 ||
|
||||
!matchesAt(header, 0, BEGIN_PREFIX) ||
|
||||
!matchesAt(header, header.length - DASH.length, DASH)) {
|
||||
throw new IOException("Illegal header: " +
|
||||
new String(header, StandardCharsets.ISO_8859_1));
|
||||
}
|
||||
} while (hyphen < 5);
|
||||
|
||||
sb.append("-----");
|
||||
String header = sb.toString();
|
||||
if (header.length() < 16 || !header.startsWith("-----BEGIN ") ||
|
||||
!header.endsWith("-----")) {
|
||||
throw new IOException("Illegal header: " + header);
|
||||
}
|
||||
hyphen = 0;
|
||||
readbuf.reset();
|
||||
|
||||
hyphen = 0;
|
||||
sb = new StringBuilder(1024);
|
||||
// Determine the line break using the char after the last hyphen
|
||||
while (eol == 0) {
|
||||
switch (is.read()) {
|
||||
case '\s', '\t' -> {} // skip whitespace or tab
|
||||
case '\r' -> {
|
||||
c = is.read();
|
||||
if (c == '\n') {
|
||||
eol = '\n';
|
||||
} else {
|
||||
eol = '\r';
|
||||
pem.write(c);
|
||||
}
|
||||
}
|
||||
case '\n' -> eol = '\n';
|
||||
default -> throw new IOException("No EOL character found");
|
||||
}
|
||||
}
|
||||
|
||||
// Determine the line break using the char after the last hyphen
|
||||
while (eol == 0) {
|
||||
switch (is.read()) {
|
||||
case '\s', '\t' -> {} // skip whitespace or tab
|
||||
case '\r' -> {
|
||||
c = is.read();
|
||||
if (c == '\n') {
|
||||
eol = '\n';
|
||||
} else {
|
||||
eol = '\r';
|
||||
sb.append((char) c);
|
||||
// Read data until we find the first footer hyphen.
|
||||
// CR & LF are allowed to support legacy PEM formats (ie: encrypted PKCS1)
|
||||
do {
|
||||
switch (c = is.read()) {
|
||||
case -1 -> throw new EOFException("Incomplete header");
|
||||
case '-' -> hyphen++;
|
||||
default -> {
|
||||
// If reading a legacy format, allow for one dash
|
||||
if (hyphen == 1) {
|
||||
hyphen = 0;
|
||||
pem.write('-');
|
||||
}
|
||||
pem.write(c);
|
||||
}
|
||||
}
|
||||
case '\n' -> eol = '\n';
|
||||
default -> throw new IOException("No EOL character found");
|
||||
} while (hyphen < 2);
|
||||
|
||||
// Verify footer starts with 5 hyphens.
|
||||
do {
|
||||
switch (is.read()) {
|
||||
case '-' -> hyphen++;
|
||||
case -1 ->
|
||||
throw new EOFException("Input ended prematurely");
|
||||
default -> throw new IOException("Incomplete footer");
|
||||
}
|
||||
} while (hyphen < 5);
|
||||
|
||||
hyphen = 0;
|
||||
readbuf.reset();
|
||||
readbuf.writeBytes(DASH);
|
||||
|
||||
// Look for Complete header by looking for the end of the hyphens
|
||||
do {
|
||||
switch (c = is.read()) {
|
||||
case '-' -> hyphen++;
|
||||
case -1 ->
|
||||
throw new EOFException("Input ended prematurely");
|
||||
default -> readbuf.write(c);
|
||||
}
|
||||
} while (hyphen == 0);
|
||||
|
||||
// Verify ending with 5 hyphens.
|
||||
do {
|
||||
switch (is.read()) {
|
||||
case '-' -> hyphen++;
|
||||
case -1 ->
|
||||
throw new EOFException("Input ended prematurely");
|
||||
default -> throw new IOException("Incomplete footer");
|
||||
}
|
||||
} while (hyphen < 5);
|
||||
|
||||
while ((c = is.read()) != eol && c != -1) {
|
||||
// skip when eol is '\n', the line separator is likely "\r\n".
|
||||
if (c == '\r' || c == '\s' || c == '\t') {
|
||||
continue;
|
||||
}
|
||||
throw new IOException("Invalid PEM format: " +
|
||||
"No EOL char found in footer: 0x" +
|
||||
HexFormat.of().toHexDigits((byte) c));
|
||||
}
|
||||
|
||||
readbuf.writeBytes(DASH);
|
||||
byte[] footer = readbuf.toByteArray();
|
||||
if (footer.length < 14 ||
|
||||
!matchesAt(footer, 0, END_PREFIX) ||
|
||||
!matchesAt(footer, footer.length - DASH.length, DASH)) {
|
||||
|
||||
// Not an IOE because the read pointer is correctly at the end.
|
||||
throw new IOException("Illegal footer: " +
|
||||
new String(footer, StandardCharsets.ISO_8859_1));
|
||||
}
|
||||
|
||||
// Verify the object type in the header and the footer are the same.
|
||||
headerType = new String(header, 11, header.length - 16,
|
||||
StandardCharsets.ISO_8859_1);
|
||||
footerType = new String(footer, 9, footer.length - 14,
|
||||
StandardCharsets.ISO_8859_1);
|
||||
if (!headerType.equals(footerType)) {
|
||||
throw new IOException("Header and footer do not " +
|
||||
"match: " + headerType + " " + footerType);
|
||||
}
|
||||
|
||||
// If there was data before finding the 5 dashes of the PEM header,
|
||||
// backup 5 characters and save that data.
|
||||
byte[] preData = null;
|
||||
if (os.size() > 6) {
|
||||
preData = Arrays.copyOf(os.getBuffer(), os.size() - 6);
|
||||
}
|
||||
|
||||
encoding = pem.toByteArray();
|
||||
return (preData == null) ?
|
||||
new PEM(typeConverter(headerType), encoding) :
|
||||
new PEM(typeConverter(headerType), encoding, preData);
|
||||
} finally {
|
||||
KeyUtil.clear(encoding);
|
||||
os.close();
|
||||
pem.clear();
|
||||
pem.close();
|
||||
readbuf.close();
|
||||
}
|
||||
|
||||
// Read data until we find the first footer hyphen.
|
||||
do {
|
||||
switch (c = is.read()) {
|
||||
case -1 ->
|
||||
throw new EOFException("Incomplete header");
|
||||
case '-' -> hyphen++;
|
||||
case '\s', '\t', '\r', '\n' -> {} // skip whitespace and tab
|
||||
default -> sb.append((char) c);
|
||||
}
|
||||
} while (hyphen == 0);
|
||||
|
||||
String data = sb.toString();
|
||||
|
||||
// Verify footer starts with 5 hyphens.
|
||||
do {
|
||||
switch (is.read()) {
|
||||
case '-' -> hyphen++;
|
||||
case -1 -> throw new EOFException("Input ended prematurely");
|
||||
default -> throw new IOException("Incomplete footer");
|
||||
}
|
||||
} while (hyphen < 5);
|
||||
|
||||
hyphen = 0;
|
||||
sb = new StringBuilder(64);
|
||||
sb.append("-----");
|
||||
|
||||
// Look for Complete header by looking for the end of the hyphens
|
||||
do {
|
||||
switch (c = is.read()) {
|
||||
case '-' -> hyphen++;
|
||||
case -1 -> throw new EOFException("Input ended prematurely");
|
||||
default -> sb.append((char) c);
|
||||
}
|
||||
} while (hyphen == 0);
|
||||
|
||||
// Verify ending with 5 hyphens.
|
||||
do {
|
||||
switch (is.read()) {
|
||||
case '-' -> hyphen++;
|
||||
case -1 -> throw new EOFException("Input ended prematurely");
|
||||
default -> throw new IOException("Incomplete footer");
|
||||
}
|
||||
} while (hyphen < 5);
|
||||
|
||||
while ((c = is.read()) != eol && c != -1 && c != '\s' && c != '\t') {
|
||||
// skip when eol is '\n', the line separator is likely "\r\n".
|
||||
if (c == '\r') {
|
||||
continue;
|
||||
}
|
||||
throw new IOException("Invalid PEM format: " +
|
||||
"No EOL char found in footer: 0x" +
|
||||
HexFormat.of().toHexDigits((byte) c));
|
||||
}
|
||||
|
||||
sb.append("-----");
|
||||
String footer = sb.toString();
|
||||
if (footer.length() < 14 || !footer.startsWith("-----END ") ||
|
||||
!footer.endsWith("-----")) {
|
||||
// Not an IOE because the read pointer is correctly at the end.
|
||||
throw new IOException("Illegal footer: " + footer);
|
||||
}
|
||||
|
||||
// Verify the object type in the header and the footer are the same.
|
||||
String headerType = header.substring(11, header.length() - 5);
|
||||
String footerType = footer.substring(9, footer.length() - 5);
|
||||
if (!headerType.equals(footerType)) {
|
||||
throw new IOException("Header and footer do not " +
|
||||
"match: " + headerType + " " + footerType);
|
||||
}
|
||||
|
||||
// If there was data before finding the 5 dashes of the PEM header,
|
||||
// backup 5 characters and save that data.
|
||||
byte[] preData = null;
|
||||
if (os.size() > 6) {
|
||||
preData = Arrays.copyOf(os.toByteArray(), os.size() - 6);
|
||||
}
|
||||
|
||||
return new PEM(typeConverter(headerType), data, preData);
|
||||
}
|
||||
|
||||
public static PEM readPEM(InputStream is) throws IOException {
|
||||
return readPEM(is, false);
|
||||
}
|
||||
|
||||
private static String pemEncoded(String type, String base64) {
|
||||
return
|
||||
"-----BEGIN " + type + "-----\r\n" +
|
||||
base64 + (!base64.endsWith("\n") ? "\r\n" : "") +
|
||||
"-----END " + type + "-----\r\n";
|
||||
/**
|
||||
* Return a PEM encoding with the given type and base64 byte array.
|
||||
*/
|
||||
public static byte[] pemEncoded(String type, byte[] base64) {
|
||||
byte[] header = ("-----BEGIN " + type + "-----\r\n")
|
||||
.getBytes(StandardCharsets.ISO_8859_1);
|
||||
byte[] footer = ("-----END " + type + "-----\r\n")
|
||||
.getBytes(StandardCharsets.ISO_8859_1);
|
||||
|
||||
int crlfLen = (base64.length == 0 ||
|
||||
base64[base64.length - 1] != '\n') ? 2 : 0;
|
||||
byte[] result = new byte[header.length + base64.length +
|
||||
crlfLen + footer.length];
|
||||
System.arraycopy(header, 0, result, 0, header.length);
|
||||
System.arraycopy(base64, 0, result, header.length, base64.length);
|
||||
if (crlfLen == 2) {
|
||||
result[header.length + base64.length] = '\r';
|
||||
result[header.length + base64.length + 1] = '\n';
|
||||
}
|
||||
System.arraycopy(footer, 0, result,
|
||||
header.length + base64.length + crlfLen, footer.length);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a String-based encoding based off the type. leadingData
|
||||
* is not used with this method.
|
||||
* @return PEM in a string
|
||||
*/
|
||||
public static String pemEncoded(String type, byte[] der) {
|
||||
public static byte[] pemEncodedFromDER(String type, byte[] der) {
|
||||
if (b64Encoder == null) {
|
||||
b64Encoder = Base64.getMimeEncoder(64, CRLF);
|
||||
}
|
||||
return pemEncoded(type, b64Encoder.encodeToString(der));
|
||||
return KeyUtil.clear(b64Encoder.encode(der), e -> pemEncoded(type, e));
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a String-based encoding based off the type. leadingData
|
||||
* is not used with this method.
|
||||
* @return PEM in a string
|
||||
* Decrypt the EncryptedPrivateKeyInfo with the given keySpec and
|
||||
* return the PKCS#8 byte array
|
||||
*/
|
||||
public static String pemEncoded(PEM pem) {
|
||||
String p = LINE_WRAP_64_PATTERN.matcher(pem.content()).replaceAll("$1\r\n");
|
||||
return pemEncoded(pem.type(), p);
|
||||
}
|
||||
|
||||
/*
|
||||
* Get PKCS8 encoding from an encrypted private key encoding.
|
||||
*/
|
||||
public static byte[] decryptEncoding(byte[] encoded, char[] password)
|
||||
throws GeneralSecurityException {
|
||||
EncryptedPrivateKeyInfo ekpi;
|
||||
|
||||
Objects.requireNonNull(password, "password cannot be null");
|
||||
PBEKeySpec keySpec = new PBEKeySpec(password);
|
||||
try {
|
||||
ekpi = new EncryptedPrivateKeyInfo(encoded);
|
||||
return decryptEncoding(ekpi, keySpec);
|
||||
} catch (IOException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
} finally {
|
||||
keySpec.clearPassword();
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] decryptEncoding(EncryptedPrivateKeyInfo ekpi, PBEKeySpec keySpec)
|
||||
throws NoSuchAlgorithmException, InvalidKeyException {
|
||||
public static byte[] decryptEncoding(EncryptedPrivateKeyInfo ekpi,
|
||||
PBEKeySpec keySpec) throws NoSuchAlgorithmException,
|
||||
InvalidKeyException {
|
||||
|
||||
PKCS8EncodedKeySpec p8KeySpec = null;
|
||||
SecretKeyFactory skf = SecretKeyFactory.getInstance(ekpi.getAlgName());
|
||||
SecretKey sk = null;
|
||||
try {
|
||||
SecretKeyFactory skf = SecretKeyFactory.getInstance(ekpi.getAlgName());
|
||||
p8KeySpec = ekpi.getKeySpec(skf.generateSecret(keySpec));
|
||||
sk = skf.generateSecret(keySpec);
|
||||
p8KeySpec = ekpi.getKeySpec(sk);
|
||||
return p8KeySpec.getEncoded();
|
||||
} catch (InvalidKeySpecException e) {
|
||||
throw new InvalidKeyException(e);
|
||||
} finally {
|
||||
KeyUtil.destroySecretKeys(sk);
|
||||
KeyUtil.clear(p8KeySpec);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* With a given PKCS8 encoding, construct a PrivateKey or KeyPair. A
|
||||
* KeyPair is returned if requested and the encoding has a public key;
|
||||
* otherwise, a PrivateKey is returned.
|
||||
*
|
||||
* @param encoded PKCS8 encoding
|
||||
* @param pair set to true for returning a KeyPair, if possible. Otherwise,
|
||||
* return a PrivateKey
|
||||
* @param provider KeyFactory provider
|
||||
*/
|
||||
public static DEREncodable toDEREncodable(byte[] encoded, boolean pair,
|
||||
public static BinaryEncodable toPKCS8Encodable(byte[] encoded,
|
||||
Provider provider) throws InvalidKeyException {
|
||||
|
||||
PrivateKey privKey;
|
||||
PublicKey pubKey = null;
|
||||
KeyFactory kf;
|
||||
PKCS8EncodedKeySpec p8KeySpec;
|
||||
PKCS8Key p8key = new PKCS8Key(encoded);
|
||||
KeyFactory kf;
|
||||
|
||||
try {
|
||||
p8KeySpec = new PKCS8EncodedKeySpec(encoded);
|
||||
} catch (NullPointerException e) {
|
||||
p8key.clear();
|
||||
throw new InvalidKeyException("No encoding found", e);
|
||||
}
|
||||
p8KeySpec = new PKCS8EncodedKeySpec(encoded);
|
||||
|
||||
try {
|
||||
if (provider == null) {
|
||||
@ -442,12 +459,6 @@ public class Pem {
|
||||
|
||||
try {
|
||||
privKey = kf.generatePrivate(p8KeySpec);
|
||||
|
||||
// Only want the PrivateKey? then return it.
|
||||
if (!pair) {
|
||||
return privKey;
|
||||
}
|
||||
|
||||
if (p8key.hasPublicKey()) {
|
||||
// PKCS8Key.decode() has extracted the public key already
|
||||
pubKey = kf.generatePublic(
|
||||
@ -467,10 +478,43 @@ public class Pem {
|
||||
} finally {
|
||||
KeyUtil.clear(p8KeySpec, p8key);
|
||||
}
|
||||
if (pair && pubKey != null) {
|
||||
if (pubKey != null) {
|
||||
return new KeyPair(pubKey, privKey);
|
||||
}
|
||||
return privKey;
|
||||
}
|
||||
|
||||
private static boolean matchesAt(byte[] source, int offset, byte[] match) {
|
||||
for (int i = 0; i < match.length; i++) {
|
||||
if (source[offset + i] != match[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clearable ByteArrayOutputStream for temporary data. Access to the
|
||||
* internal buffer is allowed to limit data copying. Handle with care.
|
||||
*/
|
||||
private static final class ClearableBufferStream
|
||||
extends ByteArrayOutputStream {
|
||||
|
||||
ClearableBufferStream(int len) {
|
||||
super(len);
|
||||
}
|
||||
|
||||
byte[] getBuffer() {
|
||||
return buf;
|
||||
}
|
||||
|
||||
int length() {
|
||||
return count;
|
||||
}
|
||||
|
||||
void clear() {
|
||||
Arrays.fill(buf, (byte) 0);
|
||||
count = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -24,7 +24,8 @@
|
||||
*/
|
||||
|
||||
import javax.crypto.EncryptedPrivateKeyInfo;
|
||||
import java.security.DEREncodable;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.BinaryEncodable;
|
||||
import java.security.KeyPair;
|
||||
import java.security.PEM;
|
||||
import java.security.cert.X509CRL;
|
||||
@ -32,7 +33,9 @@ import java.security.cert.X509Certificate;
|
||||
import java.security.interfaces.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Base64;
|
||||
import java.util.HexFormat;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
@ -399,26 +402,26 @@ class PEMData {
|
||||
6onPAs4hkm+63dfzCojvEkALevO8J3OVX7YS5q9J1r75wDn60Ob0Zh+iiorpx8Ob
|
||||
WqcWcoJqfdLEyBT+
|
||||
-----END PRIVATE KEY-----
|
||||
""", DEREncodable.class, null);
|
||||
""", BinaryEncodable.class, null);
|
||||
|
||||
private static final Entry invalidPEM = new Entry("invalidPEM", """
|
||||
-----BEGIN INVALID PEM-----
|
||||
MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDBVS52ZSKZ0oES7twD2
|
||||
GGwRIVu3uHlGIwlu0xzFe7sgIPntca2bHfYMhgGxrlCm0q+hZANiAAQNWgwWfLX8
|
||||
8pYVjvwbfvDF9f+Oa9w6JjrfpWwFAUI6b1OPgrNUh+yXtUXnQNXnfUcIu0Os53bM
|
||||
""", DEREncodable.class, null);
|
||||
""", BinaryEncodable.class, null);
|
||||
|
||||
private static final Entry invalidHeader = new Entry("invalidHeader", """
|
||||
---BEGIN PRIVATE KEY---
|
||||
MC4CAQAwBQYDK2VwBCIEIFFZsmD+OKk67Cigc84/2fWtlKsvXWLSoMJ0MHh4jI4I
|
||||
-----END PRIVATE KEY-----
|
||||
""", DEREncodable.class, null);
|
||||
""", BinaryEncodable.class, null);
|
||||
|
||||
private static final Entry invalidFooter = new Entry("invalidFooter", """
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MC4CAQAwBQYDK2VwBCIEIFFZsmD+OKk67Cigc84/2fWtlKsvXWLSoMJ0MHh4jI4I
|
||||
---END PRIVATE KEY---
|
||||
""", DEREncodable.class, null);
|
||||
""", BinaryEncodable.class, null);
|
||||
|
||||
private static final Entry incorrectFooter = new Entry("incorrectFooter", """
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
@ -427,7 +430,7 @@ class PEMData {
|
||||
8pYVjvwbfvDF9f+Oa9w6JjrfpWwFAUI6b1OPgrNUh+yXtUXnQNXnfUcIu0Os53bM
|
||||
8fTqPkQl6RyWEDHeXqJK8zTBHMeBq9nLfDPSbzQgLDyC64Orn0D8exM=
|
||||
-----END PUBLIC KEY-----
|
||||
""", DEREncodable.class, null);
|
||||
""", BinaryEncodable.class, null);
|
||||
|
||||
// EC cert with explicit parameters -- Not currently supported by SunEC
|
||||
static final String ecCertEX = """
|
||||
@ -498,6 +501,41 @@ class PEMData {
|
||||
-----END CERTIFICATE REQUEST-----
|
||||
""" + postData, PEM.class, "SunEC");
|
||||
|
||||
// This PEM is not valid, it is only used to verify PEMDecoder will
|
||||
// parse the non-base64 text properly.
|
||||
public static final String encpkcs1 = """
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
Proc-Type: 4,ENCRYPTED
|
||||
DEK-Info: DES-EDE3-CBC,ECBE98FA344F0C87
|
||||
|
||||
MIIEowIBAAKCAQEAqozTLan1qFcOCWnS63jXQn5lLyGOKDv3GM11n2zkGGrChayj
|
||||
cSzB2KTlDmN9NgOyFdqGNWbSgdmXR5ToHGHYwaKubJoQIoPQcsipWDI156d3+X/8
|
||||
BxCGY8l5nYwvS4olOXc+2kEjeFF1eamnm9IQ5DHZfaFPl0ri4Yfm1YHBAbt/7HvF
|
||||
3MBjgBj1xSsSFLW4O6ws6guRVGDfKBVyyRNUhRTbSua/nEz0wAjxF2PWT+ZTHS6M
|
||||
0siYwVTuPI4/n4ItoYoahvGb9JskkXP+bc/QZJCTFYdyxF5tKqVMSdYaJTxop02p
|
||||
Jo3oeafVKSlBrr0K731xgNBKqBud44aKT5R96QIDAQABAoIBAQCD9Q/T7gOvayPm
|
||||
LqXOISJURV1emRTXloX5/8Y5QtQ8/CVjrg6Lm3ikefjsKBgR+cwJUpmyqcrIQyXk
|
||||
cZchlqdSMt/IEW/YdKqMlStJnRfOE+ok9lx2ztdcT9+0AWn6hXmFu/i6f9nE1yoQ
|
||||
py6SxnbhSJyhsnTVd1CR9Uep/InsHvYW/15WlVMD1VuCSIt9sefqXwavbAfBaqbn
|
||||
mjwBB/ulsqKhHSuRq/QWqlj+jyGqhhYmTguC1Qwt0woDbThiHtK+suCTAlGBj/A+
|
||||
IZ1U9d+VsHBcWDKBkxmlKWcJAGR3xXiKKy9vfzC+DU7L99kgay80VZarDyXgiy78
|
||||
9xMMzRMBAoGBANoxnZhu1bUFtLqTJ1HfDm6UB+1zVd2Mu4DXYdy/AHjoaCLp05OQ
|
||||
0ZeyhO/eXPT+eGpzCxkWD7465KO/QDfnp54p/NS73jaJVdWQHBhzJx1MymqURy3N
|
||||
JQeW4+ojzwSmVXcrs7Og6EBa4L+PWLpMLW2kODniCY+vp9f5LS6m8UPJAoGBAMgZ
|
||||
4rBw7B9YFZZW/EE4eos4Q7KtA5tEP6wvCq04oxfiSytWXifYX0ToPp0CHhZlWOxk
|
||||
v9a/BDGqM7AxAQJs7mmIvT5AT2V1w7oTbFPnnAo6pQtLcfaxdFFqr0h6t0sXSOKC
|
||||
rQeZAqqFqwuOyP7vT0goGlBruHkwS21NKkzCyzkhAoGAc2JjhbWu+8Cdt0CUPX5o
|
||||
ol9T5eTlFnkSuuqrTNIQzN+SGkxu341o2QDFvhdoLwLW6OwXhVeeUanROSqtKiMu
|
||||
B70Kf/EtbMephXtk8CUNHTh7nmr1TSo8F8xakHoJQts3PQL2T9qal1W3nnWOpU4d
|
||||
g+qg9TMsfTiV2OdjVlVgJskCgYBSnjV1qjojuue22hVvDFW0c7en5z2M9wHfItEi
|
||||
sjbMnrdwnklj5Dd5qPZpNz2a+59ag0Kd9OJTazXKMoF7MeTCGB4ivMTLXHNCudBJ
|
||||
WGCZ7JrGbhEQzTX8g7L5lwlk7KlANLoiX++03lm//OVKNR6j6ULsH33cM6+A4pJr
|
||||
fSYRYQKBgCr9iMTmL0x+n6AmMNecR+MhDxi99Oy0s2EBAYqN9g/8yNgwM4KR0cjz
|
||||
EcgIOtkvoTrJ9Cquvuj+O7/d2yNoH0SZQ4IYJKq47/Z4kKhwXzJnBCCCBKgkjfub
|
||||
RTQSNnSEgTaBD29l7FrhNRHX9lIKFZ23caCTBS6o3q3+KgPbq7ao
|
||||
-----END RSA PRIVATE KEY-----
|
||||
""";
|
||||
|
||||
final static Pattern CR = Pattern.compile("\r");
|
||||
final static Pattern LF = Pattern.compile("\n");
|
||||
final static Pattern LSDEFAULT = Pattern.compile(System.lineSeparator());
|
||||
@ -543,8 +581,8 @@ class PEMData {
|
||||
clazz, provider, password());
|
||||
}
|
||||
|
||||
Entry makeCR(String name) {
|
||||
return new Entry(name,
|
||||
Entry makeCR(String newName) {
|
||||
return new Entry(newName,
|
||||
Pattern.compile(System.lineSeparator()).matcher(pem).replaceAll("\r"),
|
||||
clazz, provider, password());
|
||||
}
|
||||
@ -555,6 +593,32 @@ class PEMData {
|
||||
replaceAll(""),
|
||||
clazz, provider, password());
|
||||
}
|
||||
|
||||
Entry makeValidNoCRLF(String newName) {
|
||||
int cr = pem.indexOf('\r');
|
||||
int lf = pem.indexOf('\n');
|
||||
if (cr == -1 && lf == -1) {
|
||||
throw new AssertionError("Cannot find EOL with " + newName);
|
||||
}
|
||||
|
||||
Matcher eol;
|
||||
int cut; // index right after the first EOL
|
||||
|
||||
if (lf - cr == 1) {
|
||||
eol = LSDEFAULT.matcher(pem);
|
||||
cut = lf + 1;
|
||||
} else if (cr != -1 && cr < lf) {
|
||||
eol = CR.matcher(pem);
|
||||
cut = cr + 1;
|
||||
} else {
|
||||
eol = LF.matcher(pem);
|
||||
cut = lf + 1;
|
||||
}
|
||||
|
||||
String p = pem.substring(0, cut) +
|
||||
eol.pattern().matcher(pem.substring(cut)).replaceAll("");
|
||||
return new Entry(newName, p, clazz, provider, password());
|
||||
}
|
||||
}
|
||||
|
||||
static public Entry getEntry(String varname) {
|
||||
@ -613,8 +677,8 @@ class PEMData {
|
||||
passList.addAll(entryList);
|
||||
passList.addAll(encryptedList);
|
||||
|
||||
failureEntryList.add(new Entry("emptyPEM", "", DEREncodable.class, null));
|
||||
failureEntryList.add(new Entry("nullPEM", null, DEREncodable.class, null));
|
||||
failureEntryList.add(new Entry("emptyPEM", "", BinaryEncodable.class, null));
|
||||
failureEntryList.add(new Entry("nullPEM", null, BinaryEncodable.class, null));
|
||||
failureEntryList.add(incorrectFooter);
|
||||
failureEntryList.add(invalidPEM);
|
||||
failureEntryList.add(invalidDer);
|
||||
@ -649,6 +713,18 @@ class PEMData {
|
||||
}
|
||||
}
|
||||
|
||||
static void checkResultsExact(String expected, String result) {
|
||||
try {
|
||||
if (expected.compareTo(result) != 0) {
|
||||
System.out.println("expected:\n" + expected);
|
||||
System.out.println("generated:\n" + result);
|
||||
indexDiff(expected, result);
|
||||
}
|
||||
} catch (AssertionError e) {
|
||||
throw new AssertionError("Encoder PEM mismatch ");
|
||||
}
|
||||
}
|
||||
|
||||
static void indexDiff(String a, String b) {
|
||||
String lenerr = "";
|
||||
int len = a.length();
|
||||
|
||||
@ -65,7 +65,7 @@ public class PEMDecoderTest {
|
||||
System.out.println("Decoder test withFactory:");
|
||||
PEMData.entryList.forEach(entry -> test(entry, true));
|
||||
System.out.println("Decoder test returning DEREncodable class:");
|
||||
PEMData.entryList.forEach(entry -> test(entry, DEREncodable.class));
|
||||
PEMData.entryList.forEach(entry -> test(entry, BinaryEncodable.class));
|
||||
System.out.println("Decoder test with encrypted PEM:");
|
||||
PEMData.encryptedList.forEach(PEMDecoderTest::testEncrypted);
|
||||
System.out.println("Decoder test with OAS:");
|
||||
@ -101,7 +101,7 @@ public class PEMDecoderTest {
|
||||
System.out.println("Decoder test ecsecp256 to P8EKS:");
|
||||
decr.decode(PEMData.ecsecp256.pem(), PKCS8EncodedKeySpec.class);
|
||||
|
||||
System.out.println("Checking if decode() returns the same encoding:");
|
||||
System.out.println("Checking if content() returns the same encoding:");
|
||||
PEMData.privList.forEach(PEMDecoderTest::testDERCheck);
|
||||
PEMData.oasList.forEach(PEMDecoderTest::testDERCheck);
|
||||
|
||||
@ -110,21 +110,22 @@ public class PEMDecoderTest {
|
||||
PEMData.oasList.stream().filter(e -> !e.name().endsWith("xdh"))
|
||||
.forEach(PEMDecoderTest::testSignature);
|
||||
|
||||
System.out.println("Checking if decode() returns a PKCS8Key and can generate a pub");
|
||||
System.out.println("Checking if content() returns a PKCS8Key and can generate a pub");
|
||||
PEMData.oasList.forEach(PEMDecoderTest::testPKCS8Key);
|
||||
|
||||
System.out.println("Checking if ecCSR:");
|
||||
test(PEMData.ecCSR);
|
||||
System.out.println("Checking if ecCSR with preData:");
|
||||
DEREncodable result = d.decode(PEMData.ecCSRWithData.pem(), PEM.class);
|
||||
BinaryEncodable result = d.decode(PEMData.ecCSRWithData.pem(), PEM.class);
|
||||
if (result instanceof PEM rec) {
|
||||
if (PEMData.preData.compareTo(new String(rec.leadingData())) != 0) {
|
||||
System.err.println("expected: \"" + PEMData.preData + "\"");
|
||||
System.err.println("received: \"" + new String(rec.leadingData()) + "\"");
|
||||
throw new AssertionError("ecCSRWithData preData wrong");
|
||||
}
|
||||
if (rec.content().lastIndexOf("F") > rec.content().length() - 5) {
|
||||
System.err.println("received: " + rec.content());
|
||||
String s = rec.toString();
|
||||
if (s.lastIndexOf("F") > s.length() - 5) {
|
||||
System.err.println("received: " + s);
|
||||
throw new AssertionError("ecCSRWithData: " +
|
||||
"End of PEM data has an unexpected character");
|
||||
}
|
||||
@ -165,16 +166,6 @@ public class PEMDecoderTest {
|
||||
}
|
||||
System.out.println("PASS");
|
||||
|
||||
System.out.println("Check if PEM decode decodes content correctly");
|
||||
final String tmpContent = "1234567";
|
||||
final PEM tmpPem = new PEM("ONE", tmpContent);
|
||||
if (!Arrays.equals(
|
||||
Base64.getDecoder().decode(tmpContent),
|
||||
tmpPem.decode())) {
|
||||
throw new AssertionError("PEM decode error");
|
||||
}
|
||||
System.out.println("PASS");
|
||||
|
||||
System.out.println("Decode to EncryptedPrivateKeyInfo: ");
|
||||
EncryptedPrivateKeyInfo ekpi =
|
||||
d.decode(PEMData.ed25519ep8.pem(), EncryptedPrivateKeyInfo.class);
|
||||
@ -217,7 +208,13 @@ public class PEMDecoderTest {
|
||||
bos.write("--------\n".getBytes(StandardCharsets.ISO_8859_1));
|
||||
bos.write(PEMData.ecsecp256ekpi.pem().getBytes(StandardCharsets.ISO_8859_1));
|
||||
bis = new ByteArrayInputStream(bos.toByteArray());
|
||||
result = d.decode(bis, PEM.class);
|
||||
d.decode(bis, PEM.class);
|
||||
|
||||
d.decode(PEMData.encpkcs1, PEM.class);
|
||||
try {
|
||||
d.decode(PEMData.ecCSR.pem(), BinaryEncodable.class);
|
||||
throw new AssertionError("decode(s, BinaryEncodable.class passed");
|
||||
} catch (ClassCastException _) {}
|
||||
}
|
||||
|
||||
static void testInputStream() throws IOException {
|
||||
@ -287,12 +284,14 @@ public class PEMDecoderTest {
|
||||
|
||||
static void testPEMRecord(PEMData.Entry entry) {
|
||||
PEM r = d.decode(entry.pem(), PEM.class);
|
||||
String expected = entry.pem().split("-----")[2].replace(System.lineSeparator(), "");
|
||||
int start = entry.pem().indexOf("-----");
|
||||
int end = entry.pem().lastIndexOf("-----");
|
||||
String expected = entry.pem().substring(start, end);
|
||||
try {
|
||||
PEMData.checkResults(expected, r.content());
|
||||
PEMData.checkResults(expected, r.toString());
|
||||
} catch (AssertionError e) {
|
||||
System.err.println("expected:\n" + expected);
|
||||
System.err.println("received:\n" + r.content());
|
||||
System.err.println("received:\n" + r);
|
||||
throw e;
|
||||
}
|
||||
|
||||
@ -322,7 +321,7 @@ public class PEMDecoderTest {
|
||||
|
||||
static void testPEMRecordDecode(PEMData.Entry entry) {
|
||||
PEM r = d.decode(entry.pem(), PEM.class);
|
||||
DEREncodable de = d.decode(r.toString());
|
||||
BinaryEncodable de = d.decode(r.toString());
|
||||
|
||||
boolean result = switch(r.type()) {
|
||||
case Pem.PRIVATE_KEY ->
|
||||
@ -371,7 +370,7 @@ public class PEMDecoderTest {
|
||||
}
|
||||
}
|
||||
|
||||
static DEREncodable testEncrypted(PEMData.Entry entry) {
|
||||
static BinaryEncodable testEncrypted(PEMData.Entry entry) {
|
||||
PEMDecoder decoder;
|
||||
if (!Objects.equals(entry.clazz(), EncryptedPrivateKeyInfo.class)) {
|
||||
decoder = d.withDecryption(entry.password());
|
||||
@ -388,27 +387,27 @@ public class PEMDecoderTest {
|
||||
}
|
||||
|
||||
// Change the Entry to use the given class as the expected class returned
|
||||
static DEREncodable test(PEMData.Entry entry, Class c) {
|
||||
static BinaryEncodable test(PEMData.Entry entry, Class c) {
|
||||
return test(entry.newClass(c), false);
|
||||
}
|
||||
|
||||
// Run test with a given Entry
|
||||
static DEREncodable test(PEMData.Entry entry) {
|
||||
static BinaryEncodable test(PEMData.Entry entry) {
|
||||
return test(entry, false);
|
||||
}
|
||||
|
||||
// Run test with a given Entry
|
||||
static DEREncodable test(PEMData.Entry entry, boolean withFactory) {
|
||||
static BinaryEncodable test(PEMData.Entry entry, boolean withFactory) {
|
||||
System.out.printf("Testing %s %s%n", entry.name(), entry.provider());
|
||||
try {
|
||||
PEMDecoder pemDecoder;
|
||||
if (withFactory) {
|
||||
Provider provider = Security.getProvider(entry.provider());
|
||||
pemDecoder = d.withFactory(provider);
|
||||
pemDecoder = d.withFactoriesOf(provider);
|
||||
} else {
|
||||
pemDecoder = d;
|
||||
}
|
||||
DEREncodable r = test(entry.pem(), entry.clazz(), pemDecoder);
|
||||
BinaryEncodable r = test(entry.pem(), entry.clazz(), pemDecoder);
|
||||
System.out.println("PASS (" + entry.name() + ")");
|
||||
return r;
|
||||
} catch (Exception | AssertionError e) {
|
||||
@ -444,9 +443,9 @@ public class PEMDecoderTest {
|
||||
* Perform the decoding test with the given decoder, on the given pem, and
|
||||
* expect the clazz to be returned.
|
||||
*/
|
||||
static DEREncodable test(String pem, Class clazz, PEMDecoder decoder)
|
||||
static BinaryEncodable test(String pem, Class clazz, PEMDecoder decoder)
|
||||
throws IOException {
|
||||
DEREncodable pk = decoder.decode(pem);
|
||||
BinaryEncodable pk = decoder.decode(pem);
|
||||
|
||||
// Check that clazz matches what pk returned.
|
||||
if (pk.getClass().equals(clazz)) {
|
||||
@ -553,7 +552,7 @@ public class PEMDecoderTest {
|
||||
byte[] data = "12345678".getBytes();
|
||||
PrivateKey privateKey;
|
||||
|
||||
DEREncodable der = d.decode(entry.pem());
|
||||
BinaryEncodable der = d.decode(entry.pem());
|
||||
switch (der) {
|
||||
case PrivateKey p -> privateKey = p;
|
||||
case KeyPair kp -> privateKey = kp.getPrivate();
|
||||
|
||||
@ -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
|
||||
@ -56,7 +56,7 @@ import static jdk.test.lib.Asserts.assertThrows;
|
||||
|
||||
public class PEMEncoderTest {
|
||||
|
||||
static Map<String, DEREncodable> keymap;
|
||||
static Map<String, BinaryEncodable> keymap;
|
||||
static String pkcs8DefaultAlgExpect;
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
@ -79,8 +79,12 @@ public class PEMEncoderTest {
|
||||
keymap.keySet().forEach(key -> test(key, PEMEncoder.of()));
|
||||
System.out.println("Same instance re-encode testToString:");
|
||||
keymap.keySet().forEach(key -> testToString(key, encoder));
|
||||
System.out.println("Same instance encode/encodeToString consistency test:");
|
||||
keymap.keySet().forEach(key -> testEncodeConsistency(key, encoder));
|
||||
System.out.println("New instance re-encode testToString:");
|
||||
keymap.keySet().forEach(key -> testToString(key, PEMEncoder.of()));
|
||||
System.out.println("New instance encode/encodeToString consistency test:");
|
||||
keymap.keySet().forEach(key -> testEncodeConsistency(key, PEMEncoder.of()));
|
||||
System.out.println("Same instance Encoder testEncodedKeySpec:");
|
||||
testEncodedKeySpec(encoder);
|
||||
System.out.println("New instance Encoder testEncodedKeySpec:");
|
||||
@ -90,6 +94,8 @@ public class PEMEncoderTest {
|
||||
keymap = generateObjKeyMap(PEMData.encryptedList);
|
||||
System.out.println("Same instance Encoder match test:");
|
||||
keymap.keySet().forEach(key -> testEncryptedMatch(key, encoder));
|
||||
System.out.println("Same instance encrypted encode/encodeToString consistency test:");
|
||||
keymap.keySet().forEach(key -> testEncodeConsistency(key, encoder));
|
||||
System.out.println("Same instance Encoder new withEnc test:");
|
||||
keymap.keySet().forEach(key -> testEncrypted(key, encoder));
|
||||
System.out.println("New instance Encoder and withEnc test:");
|
||||
@ -150,10 +156,36 @@ public class PEMEncoderTest {
|
||||
throw new AssertionError("encoder tried to encrypt " +
|
||||
"an EncryptedPrivateKeyInfo.");
|
||||
} catch (IllegalArgumentException _) {}
|
||||
|
||||
// Check PEM string exact
|
||||
String expected = encoder.encodeToString(decoder.decode(
|
||||
PEMData.ecsecp256.pem()));
|
||||
PEMData.Entry e = PEMData.ecsecp256.makeCRLF("ecsecp256CRLF");
|
||||
System.out.println("Exact PEM String check with CRLF only PEM:");
|
||||
PEMData.checkResultsExact(expected, encoder.encodeToString(
|
||||
decoder.decode(e.pem())));
|
||||
System.out.println("Exact PEM String check with CR only PEM:");
|
||||
e = PEMData.ecsecp256.makeCR("ecsecp256CR");
|
||||
PEMData.checkResultsExact(expected, encoder.encodeToString(
|
||||
decoder.decode(e.pem())));
|
||||
System.out.println("Exact PEM String check with NoCRLF only PEM:");
|
||||
e = PEMData.ecsecp256.makeValidNoCRLF("ecsecp256ValidNoCRLF");
|
||||
System.out.println(HexFormat.of().formatHex(e.pem().getBytes(StandardCharsets.UTF_8)));
|
||||
System.out.println("EOL: " + HexFormat.of().formatHex(System.lineSeparator().getBytes(StandardCharsets.UTF_8)));
|
||||
PEMData.checkResultsExact(expected, encoder.encodeToString(
|
||||
decoder.decode(e.pem())));
|
||||
|
||||
// Independent structural check for the new byte-oriented utility path.
|
||||
System.out.println("Testing consistency between pemEncodedFromArray()" +
|
||||
"and pemEncoded():");
|
||||
testPemEncodedFromArray();
|
||||
|
||||
// Encode an empty PEM content
|
||||
encoder.encode(new PEM("X", ""));
|
||||
}
|
||||
|
||||
static Map generateObjKeyMap(List<PEMData.Entry> list) {
|
||||
Map<String, DEREncodable> keymap = new HashMap<>();
|
||||
Map<String, BinaryEncodable> keymap = new HashMap<>();
|
||||
PEMDecoder pemd = PEMDecoder.of();
|
||||
for (PEMData.Entry entry : list) {
|
||||
try {
|
||||
@ -202,6 +234,49 @@ public class PEMEncoderTest {
|
||||
System.out.println("PASS: " + entry.name());
|
||||
}
|
||||
|
||||
static void testEncodeConsistency(String key, PEMEncoder encoder) {
|
||||
byte[] encoding;
|
||||
String pem;
|
||||
PEMData.Entry entry = PEMData.getEntry(key);
|
||||
try {
|
||||
encoding = encoder.encode(keymap.get(key));
|
||||
pem = encoder.encodeToString(keymap.get(key));
|
||||
} catch (RuntimeException e) {
|
||||
throw new AssertionError("Encoder consistency failure with " +
|
||||
entry.name(), e);
|
||||
}
|
||||
|
||||
assertEquals(new String(encoding, StandardCharsets.ISO_8859_1), pem);
|
||||
System.out.println("PASS: " + entry.name());
|
||||
}
|
||||
|
||||
static void testPemEncodedFromArray() {
|
||||
byte[] data = {1, 2, 3, 4, 5};
|
||||
String type = Pem.CERTIFICATE;
|
||||
String base64 = Base64.getMimeEncoder(64, "\r\n".getBytes(
|
||||
StandardCharsets.ISO_8859_1)).encodeToString(data);
|
||||
var expected = ("-----BEGIN " + type + "-----\r\n" +
|
||||
base64 + (!base64.endsWith("\n") ? "\r\n" : "") +
|
||||
"-----END " + type + "-----\r\n");
|
||||
var result = Pem.pemEncoded(type, base64.getBytes(StandardCharsets.ISO_8859_1));
|
||||
if (!Arrays.equals(result, expected.getBytes(StandardCharsets.ISO_8859_1))) {
|
||||
throw new AssertionError(
|
||||
"result =\n" + new String(result, StandardCharsets.ISO_8859_1) +
|
||||
"expected =\n " + expected);
|
||||
}
|
||||
|
||||
// Empty data should still include a CRLF before footer.
|
||||
byte[] empty = new byte[0];
|
||||
String emptyBase64 = Base64.getMimeEncoder(64, "\r\n".getBytes(
|
||||
StandardCharsets.ISO_8859_1)).encodeToString(empty);
|
||||
String emptyExpected = "-----BEGIN " + type + "-----\r\n" +
|
||||
emptyBase64 + (!emptyBase64.endsWith("\n") ? "\r\n" : "") +
|
||||
"-----END " + type + "-----\r\n";
|
||||
assertEquals(new String(Pem.pemEncoded(type, empty),
|
||||
StandardCharsets.ISO_8859_1), emptyExpected);
|
||||
System.out.println("PASS");
|
||||
}
|
||||
|
||||
/*
|
||||
Test cannot verify PEM was the same as known PEM because we have no
|
||||
public access to the AlgoritmID.params and PBES2Parameters.
|
||||
@ -237,7 +312,7 @@ public class PEMEncoderTest {
|
||||
try {
|
||||
encoder.encodeToString(keymap.get(key));
|
||||
} catch (RuntimeException e) {
|
||||
throw new AssertionError("Encrypted encoder failured with " +
|
||||
throw new AssertionError("Encrypted encoder failed with " +
|
||||
entry.name(), e);
|
||||
}
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2009, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2009, 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
|
||||
@ -41,13 +41,11 @@
|
||||
*/
|
||||
|
||||
import java.io.*;
|
||||
import java.net.SocketException;
|
||||
import java.security.DEREncodable;
|
||||
import java.security.BinaryEncodable;
|
||||
import java.security.PEMDecoder;
|
||||
import java.util.*;
|
||||
import java.security.Security;
|
||||
import java.security.cert.*;
|
||||
import java.security.cert.CertPathValidatorException.BasicReason;
|
||||
import sun.security.util.DerInputStream;
|
||||
|
||||
/**
|
||||
@ -161,7 +159,7 @@ public final class DisableRevocation {
|
||||
}
|
||||
|
||||
private static CertStore generateCertificateStore() throws Exception {
|
||||
Collection<DEREncodable> entries = new HashSet<>();
|
||||
Collection<BinaryEncodable> entries = new HashSet<>();
|
||||
|
||||
// generate certificate from certificate string
|
||||
entries.add(PEM_DECODER.decode(targetCertStr, X509Certificate.class));
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2009, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2009, 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
|
||||
@ -40,14 +40,12 @@
|
||||
* @author Xuelei Fan
|
||||
*/
|
||||
|
||||
import java.io.*;
|
||||
import java.net.SocketException;
|
||||
import java.security.DEREncodable;
|
||||
import java.security.BinaryEncodable;
|
||||
import java.security.PEMDecoder;
|
||||
import java.util.*;
|
||||
import java.security.Security;
|
||||
import java.security.cert.*;
|
||||
import java.security.cert.CertPathValidatorException.BasicReason;
|
||||
|
||||
import sun.security.util.DerInputStream;
|
||||
|
||||
/**
|
||||
@ -196,7 +194,7 @@ public final class KeyUsageMatters {
|
||||
}
|
||||
|
||||
private static CertStore generateCertificateStore() throws Exception {
|
||||
Collection<DEREncodable> entries = new HashSet<>();
|
||||
Collection<BinaryEncodable> entries = new HashSet<>();
|
||||
|
||||
// Decode and add certificates
|
||||
entries.add(PEM_DECODER.decode(targetCertStr, X509Certificate.class));
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2009, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2009, 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
|
||||
@ -39,7 +39,7 @@
|
||||
* @run main/othervm StatusLoopDependency alice
|
||||
*/
|
||||
|
||||
import java.security.DEREncodable;
|
||||
import java.security.BinaryEncodable;
|
||||
import java.security.PEMDecoder;
|
||||
import java.security.Security;
|
||||
import java.security.cert.CertPathBuilder;
|
||||
@ -210,9 +210,9 @@ public final class StatusLoopDependency {
|
||||
|
||||
private static CertStore generateCertificateStore() throws Exception {
|
||||
|
||||
Collection<DEREncodable> entries = new HashSet<>();
|
||||
Collection<BinaryEncodable> entries = new HashSet<>();
|
||||
|
||||
DEREncodable cert = pemDecoder.decode(targetCertStr, X509Certificate.class);
|
||||
BinaryEncodable cert = pemDecoder.decode(targetCertStr, X509Certificate.class);
|
||||
entries.add(cert);
|
||||
|
||||
cert = pemDecoder.decode(subCaCertStr, X509Certificate.class);
|
||||
@ -228,7 +228,7 @@ public final class StatusLoopDependency {
|
||||
entries.add(cert);
|
||||
|
||||
// generate CRL from CRL string
|
||||
DEREncodable mixes = pemDecoder.decode(topCrlStr, X509CRL.class);
|
||||
BinaryEncodable mixes = pemDecoder.decode(topCrlStr, X509CRL.class);
|
||||
entries.add(mixes);
|
||||
|
||||
mixes = pemDecoder.decode(subCrlStr, X509CRL.class);
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2009, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2009, 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
|
||||
@ -136,13 +136,11 @@
|
||||
*/
|
||||
|
||||
import java.io.*;
|
||||
import java.net.SocketException;
|
||||
import java.security.DEREncodable;
|
||||
import java.security.BinaryEncodable;
|
||||
import java.security.PEMDecoder;
|
||||
import java.util.*;
|
||||
import java.security.Security;
|
||||
import java.security.cert.*;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.cert.CertPathValidatorException.BasicReason;
|
||||
import java.util.Collections;
|
||||
|
||||
@ -232,7 +230,7 @@ public class FailoverToCRL {
|
||||
new ByteArrayInputStream(crlStr.getBytes());
|
||||
|
||||
// generate a cert store
|
||||
Collection<DEREncodable> crls = new HashSet<>();
|
||||
Collection<BinaryEncodable> crls = new HashSet<>();
|
||||
|
||||
crls.add(PEM_DECODER.decode(crlStr, X509CRL.class));
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2009, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2009, 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,9 +37,7 @@
|
||||
* @author Xuelei Fan
|
||||
*/
|
||||
|
||||
import java.io.*;
|
||||
import java.net.SocketException;
|
||||
import java.security.DEREncodable;
|
||||
import java.security.BinaryEncodable;
|
||||
import java.security.PEMDecoder;
|
||||
import java.util.*;
|
||||
import java.security.Security;
|
||||
@ -142,7 +140,7 @@ public class CircularCRLOneLevel {
|
||||
private static CertStore generateCertificateStore() throws Exception {
|
||||
|
||||
// generate a cert store
|
||||
Collection<DEREncodable> entries = new HashSet<>();
|
||||
Collection<BinaryEncodable> entries = new HashSet<>();
|
||||
entries.add(PEM_DECODER.decode(crlStr, X509CRL.class));
|
||||
entries.add(PEM_DECODER.decode(crlIssuerCertStr, X509Certificate.class));
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2009, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2009, 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,9 +37,7 @@
|
||||
* @author Xuelei Fan
|
||||
*/
|
||||
|
||||
import java.io.*;
|
||||
import java.net.SocketException;
|
||||
import java.security.DEREncodable;
|
||||
import java.security.BinaryEncodable;
|
||||
import java.security.PEMDecoder;
|
||||
import java.util.*;
|
||||
import java.security.Security;
|
||||
@ -142,7 +140,7 @@ public class CircularCRLOneLevelRevoked {
|
||||
|
||||
private static CertStore generateCertificateStore() throws Exception {
|
||||
// generate CRL from CRL string
|
||||
Collection<DEREncodable> entries = new HashSet<>();
|
||||
Collection<BinaryEncodable> entries = new HashSet<>();
|
||||
entries.add(PEM_DECODER.decode(crlStr, X509CRL.class));
|
||||
entries.add(PEM_DECODER.decode(crlIssuerCertStr, X509Certificate.class));
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2009, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2009, 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 @@
|
||||
* @author Xuelei Fan
|
||||
*/
|
||||
|
||||
import java.security.DEREncodable;
|
||||
import java.security.BinaryEncodable;
|
||||
import java.security.PEMDecoder;
|
||||
import java.security.Security;
|
||||
import java.security.cert.CertPath;
|
||||
@ -198,11 +198,11 @@ public class CircularCRLTwoLevel {
|
||||
}
|
||||
|
||||
private static CertStore generateCertificateStore() throws Exception {
|
||||
Collection<DEREncodable> entries = new HashSet<>();
|
||||
Collection<BinaryEncodable> entries = new HashSet<>();
|
||||
|
||||
// generate CRL from CRL string
|
||||
|
||||
DEREncodable mixes = pemDecoder.decode(topCrlStr, X509CRL.class);
|
||||
BinaryEncodable mixes = pemDecoder.decode(topCrlStr, X509CRL.class);
|
||||
entries.add(mixes);
|
||||
|
||||
mixes = pemDecoder.decode(subCrlStr, X509CRL.class);
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2009, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2009, 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 @@
|
||||
* @author Xuelei Fan
|
||||
*/
|
||||
|
||||
import java.security.DEREncodable;
|
||||
import java.security.BinaryEncodable;
|
||||
import java.security.PEMDecoder;
|
||||
import java.security.Security;
|
||||
import java.security.cert.CertPath;
|
||||
@ -198,12 +198,12 @@ public class CircularCRLTwoLevelRevoked {
|
||||
}
|
||||
|
||||
private static CertStore generateCertificateStore() throws Exception {
|
||||
Collection<DEREncodable> entries = new HashSet<>();
|
||||
Collection<BinaryEncodable> entries = new HashSet<>();
|
||||
|
||||
// generate CRL from CRL string
|
||||
CertificateFactory cf = CertificateFactory.getInstance("X.509");
|
||||
|
||||
DEREncodable mixes = pemDecoder.decode(topCrlStr, X509CRL.class);
|
||||
BinaryEncodable mixes = pemDecoder.decode(topCrlStr, X509CRL.class);
|
||||
entries.add(mixes);
|
||||
|
||||
mixes = pemDecoder.decode(subCrlStr, X509CRL.class);
|
||||
|
||||
@ -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
|
||||
@ -129,7 +129,7 @@ public class Encrypt {
|
||||
// Test encryptKey(PrivateKey, Key, String, ...) with provider and SecureRandom
|
||||
e = EncryptedPrivateKeyInfo.encrypt(priKey, key2, "AES_128/GCM/NoPadding",
|
||||
null, p, new SecureRandom());
|
||||
PrivateKey key3 = e.getKey(key2, null);
|
||||
PrivateKey key3 = e.getKey(key2);
|
||||
assertEquals(key3, priKey, "AES encryption failed");
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
@ -35,8 +35,6 @@ import javax.crypto.SecretKey;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.security.PEMDecoder;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.Provider;
|
||||
import java.security.Security;
|
||||
import java.util.Arrays;
|
||||
|
||||
public class GetKey {
|
||||
@ -50,30 +48,13 @@ public class GetKey {
|
||||
IycFtI70ciPjgwDSjtCcPxR8fSxJPrm2yOJsRVo=
|
||||
-----END ENCRYPTED PRIVATE KEY-----
|
||||
""";
|
||||
private static final String encDHECKey =
|
||||
"""
|
||||
-----BEGIN ENCRYPTED PRIVATE KEY-----
|
||||
MIIBvDBmBgkqhkiG9w0BBQ0wWTA4BgkqhkiG9w0BBQwwKwQUN8pkErJx7aqH0fJF
|
||||
BcOadPKiuRoCAhAAAgEQMAwGCCqGSIb3DQIJBQAwHQYJYIZIAWUDBAECBBAT1Vwd
|
||||
gU4rTd6zy7lKr4wmBIIBUMe+2+O0AG6t4CMSHcDVceRg2jvbs5PmPjW4Ka5mDich
|
||||
hVEsjSpJLbUyJdbji6UaiUpuWgvYSMLZ10pfhOFw/ssXwCw+JrlXUqDpQGLaW8ZR
|
||||
zSL3CoozTI2Y6EBdWt53KbySwtZMoTpW/W3vPi98bJXtR635msf6gYXmSUP7DyoJ
|
||||
79dxz3pRYsnOuBe0yZ2wTq9iMgTMudzLJAFX2qyi+3KOb1g5Va9DYAqJmzCYOd74
|
||||
+I+0gGNFtSc1vGQYr3cAfcKT8AZ1RHE4IkpnpgFD5HsZ8f4hy0yK8juk9NE9Gzuy
|
||||
B929LBXk6V3L0MKzIABS3QvAlhWETM6XtGBDugzAgsooo9lEHLwYRldvOlL+QYyE
|
||||
CtqDmXOrgEMWvxWGEFCTKYhKkqMKjU3y3GiozEEdb9j2okW1s30yHQjIoj0OR4nB
|
||||
D8GeP0QnY73NfbOw7z81TA==
|
||||
-----END ENCRYPTED PRIVATE KEY-----
|
||||
""";
|
||||
|
||||
private static final String passwdText = "fish";
|
||||
private static final char[] password = passwdText.toCharArray();
|
||||
private static final SecretKey key = new SecretKeySpec(
|
||||
passwdText.getBytes(), "PBE");
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
Provider p = Security.getProvider(
|
||||
System.getProperty("test.provider.name", "SunJCE"));
|
||||
|
||||
EncryptedPrivateKeyInfo ekpi = PEMDecoder.of().decode(encEdECKey,
|
||||
EncryptedPrivateKeyInfo.class);
|
||||
PrivateKey priKey = PEMDecoder.of().withDecryption(password).
|
||||
@ -86,23 +67,11 @@ public class GetKey {
|
||||
+ "match with expected.");
|
||||
}
|
||||
|
||||
// Test getKey(key, provider) provider null
|
||||
// Test getKey(EdEC)
|
||||
if (!Arrays.equals(priKey.getEncoded(),
|
||||
ekpi.getKey(key, null).getEncoded())) {
|
||||
throw new AssertionError("getKey(key, provider) " +
|
||||
ekpi.getKey(key).getEncoded())) {
|
||||
throw new AssertionError("getKey(key) " +
|
||||
"didn't match with expected.");
|
||||
}
|
||||
|
||||
// Test getKey(key, provider) with provider
|
||||
EncryptedPrivateKeyInfo ekpiDH = PEMDecoder.of().decode(encDHECKey,
|
||||
EncryptedPrivateKeyInfo.class);
|
||||
PrivateKey priKeyDH = PEMDecoder.of().withDecryption(password).
|
||||
decode(encDHECKey, PrivateKey.class);
|
||||
|
||||
if (!Arrays.equals(priKeyDH.getEncoded(),
|
||||
ekpiDH.getKey(key, p).getEncoded())) {
|
||||
throw new AssertionError("getKey(key, provider) " +
|
||||
"didn't match with expected.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
@ -61,9 +61,6 @@ public class GetKeyPair {
|
||||
static byte[] keyOrigPub, keyOrigPriv;
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
Provider p = Security.getProvider(
|
||||
System.getProperty("test.provider.name", "SunJCE"));
|
||||
|
||||
KeyPairGenerator kpg = KeyPairGenerator.getInstance("ML-KEM");
|
||||
KeyPair kpOrig = kpg.generateKeyPair();
|
||||
keyOrigPub = kpOrig.getPublic().getEncoded();
|
||||
@ -92,12 +89,8 @@ public class GetKeyPair {
|
||||
arrayCheck(ekpi.getKeyPair(password));
|
||||
|
||||
// Test getKeyPair(key, provider) provider null
|
||||
System.out.print("Testing getKeyPair(key, null): ");
|
||||
arrayCheck(ekpi.getKeyPair(key, null));
|
||||
|
||||
// Test getKeyPair(key, provider) provider SunJCE
|
||||
System.out.print("Testing getKeyPair(key, SunJCE): ");
|
||||
arrayCheck(ekpi.getKeyPair(key, p));
|
||||
System.out.print("Testing getKeyPair(key): ");
|
||||
arrayCheck(ekpi.getKeyPair(key));
|
||||
}
|
||||
|
||||
static void arrayCheck(KeyPair kp) throws Exception {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2015, 2023, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2015, 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
|
||||
@ -53,7 +53,7 @@ public class BadPem {
|
||||
pass.toCharArray());
|
||||
byte[] cert = keyStore.getCertificate(alias).getEncoded();
|
||||
|
||||
// 8074935
|
||||
// Revert 8074935. RFC 7468 allows non-base64 chars
|
||||
ByteArrayOutputStream bout = new ByteArrayOutputStream();
|
||||
PrintStream pout = new PrintStream(bout);
|
||||
byte[] CRLF = new byte[] {'\r', '\n'};
|
||||
@ -67,9 +67,8 @@ public class BadPem {
|
||||
|
||||
try {
|
||||
cf.generateCertificate(new ByteArrayInputStream(bout.toByteArray()));
|
||||
throw new Exception("Should fail");
|
||||
} catch (CertificateException e) {
|
||||
// Good
|
||||
throw new Exception("Should not fail");
|
||||
}
|
||||
|
||||
// 8208602
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2009, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2009, 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
|
||||
@ -49,7 +49,7 @@
|
||||
* @author Xuelei Fan
|
||||
*/
|
||||
|
||||
import java.security.DEREncodable;
|
||||
import java.security.BinaryEncodable;
|
||||
import java.security.PEMDecoder;
|
||||
import java.util.*;
|
||||
import java.security.Security;
|
||||
@ -340,11 +340,11 @@ public class CPBuilder {
|
||||
}
|
||||
|
||||
private static CertStore generateCertificateStore() throws Exception {
|
||||
Collection<DEREncodable> entries = new HashSet<>();
|
||||
Collection<BinaryEncodable> entries = new HashSet<>();
|
||||
|
||||
for (String key : certmap.keySet()) {
|
||||
String certStr = certmap.get(key);
|
||||
DEREncodable cert = PEM_DECODER.decode(certStr, X509Certificate.class);
|
||||
BinaryEncodable cert = PEM_DECODER.decode(certStr, X509Certificate.class);
|
||||
entries.add(cert);
|
||||
}
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2014, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2014, 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
|
||||
@ -54,7 +54,7 @@
|
||||
* certificates used in this test are generated by an updated generate.sh that
|
||||
* replacing MD2 with MD5 algorithm.
|
||||
*/
|
||||
import java.security.DEREncodable;
|
||||
import java.security.BinaryEncodable;
|
||||
import java.security.PEMDecoder;
|
||||
import java.util.*;
|
||||
import java.security.cert.*;
|
||||
@ -344,14 +344,14 @@ public class CPBuilderWithMD5 {
|
||||
}
|
||||
|
||||
private static CertStore generateCertificateStore() throws Exception {
|
||||
Collection<DEREncodable> entries = new HashSet<>();
|
||||
Collection<BinaryEncodable> entries = new HashSet<>();
|
||||
|
||||
// generate certificate from certificate string
|
||||
CertificateFactory cf = CertificateFactory.getInstance("X.509");
|
||||
|
||||
for (String key : certmap.keySet()) {
|
||||
String certStr = certmap.get(key);
|
||||
DEREncodable cert = PEM_DECODER.decode(certStr, X509Certificate.class);
|
||||
BinaryEncodable cert = PEM_DECODER.decode(certStr, X509Certificate.class);
|
||||
entries.add(cert);
|
||||
}
|
||||
|
||||
|
||||
@ -76,7 +76,7 @@ public class PSSKeyCompatibility {
|
||||
|
||||
try {
|
||||
final PEMDecoder decoder = PEMDecoder.of()
|
||||
.withFactory(Security.getProvider(provider));
|
||||
.withFactoriesOf(Security.getProvider(provider));
|
||||
final PrivateKey priv = decoder.decode(
|
||||
type,
|
||||
PrivateKey.class
|
||||
@ -119,7 +119,7 @@ public class PSSKeyCompatibility {
|
||||
System.out.println(cert);
|
||||
|
||||
final PEMDecoder decoder = PEMDecoder.of()
|
||||
.withFactory(Security.getProvider(provider));
|
||||
.withFactoriesOf(Security.getProvider(provider));
|
||||
final RSAPublicKey pub = decoder.decode(
|
||||
PEMEncoder.of().encodeToString(
|
||||
new X509EncodedKeySpec(
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user