mirror of
https://github.com/openjdk/jdk.git
synced 2026-03-14 18:03:44 +00:00
8360564: Implement JEP 524: PEM Encodings of Cryptographic Objects (Second Preview)
Reviewed-by: weijun, mullan
This commit is contained in:
parent
cc05530b81
commit
ad3dfaf1fc
@ -47,7 +47,7 @@ import java.security.spec.X509EncodedKeySpec;
|
||||
* @see EncryptedPrivateKeyInfo
|
||||
* @see X509Certificate
|
||||
* @see X509CRL
|
||||
* @see PEMRecord
|
||||
* @see PEM
|
||||
*
|
||||
* @since 25
|
||||
*/
|
||||
@ -55,5 +55,5 @@ import java.security.spec.X509EncodedKeySpec;
|
||||
@PreviewFeature(feature = PreviewFeature.Feature.PEM_API)
|
||||
public sealed interface DEREncodable permits AsymmetricKey, KeyPair,
|
||||
PKCS8EncodedKeySpec, X509EncodedKeySpec, EncryptedPrivateKeyInfo,
|
||||
X509Certificate, X509CRL, PEMRecord {
|
||||
X509Certificate, X509CRL, PEM {
|
||||
}
|
||||
|
||||
147
src/java.base/share/classes/java/security/PEM.java
Normal file
147
src/java.base/share/classes/java/security/PEM.java
Normal file
@ -0,0 +1,147 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package java.security;
|
||||
|
||||
import jdk.internal.javac.PreviewFeature;
|
||||
|
||||
import sun.security.util.Pem;
|
||||
|
||||
import java.io.InputStream;
|
||||
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.
|
||||
*
|
||||
* <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:
|
||||
* 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.
|
||||
*
|
||||
* @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}.
|
||||
*
|
||||
* @spec https://www.rfc-editor.org/info/rfc7468
|
||||
* RFC 7468: Textual Encodings of PKIX, PKCS, and CMS Structures
|
||||
*
|
||||
* @see PEMDecoder
|
||||
* @see PEMEncoder
|
||||
*
|
||||
* @since 26
|
||||
*/
|
||||
@PreviewFeature(feature = PreviewFeature.Feature.PEM_API)
|
||||
public record PEM(String type, String content, byte[] leadingData)
|
||||
implements DEREncodable {
|
||||
|
||||
/**
|
||||
* Creates a {@code PEM} instance with the specified parameters.
|
||||
*
|
||||
* @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}
|
||||
*/
|
||||
public PEM {
|
||||
Objects.requireNonNull(type, "\"type\" cannot be null.");
|
||||
Objects.requireNonNull(content, "\"content\" cannot be null.");
|
||||
|
||||
// With no validity checking on `type`, the constructor accept anything
|
||||
// including lowercase. The onus is on the caller.
|
||||
if (type.startsWith("-") || type.startsWith("BEGIN ") ||
|
||||
type.startsWith("END ")) {
|
||||
throw new IllegalArgumentException("PEM syntax labels found. " +
|
||||
"Only the PEM type identifier is allowed.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@code PEM} instance with the specified type and content. This
|
||||
* constructor sets {@code leadingData} to {@code null}.
|
||||
*
|
||||
* @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}
|
||||
*/
|
||||
public PEM(String type, String content) {
|
||||
this(type, content, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the PEM formatted string containing the {@code type} and
|
||||
* Base64-encoded {@code content}. {@code leadingData} is not included.
|
||||
*
|
||||
* @return the PEM text representation
|
||||
*/
|
||||
@Override
|
||||
final public String toString() {
|
||||
return Pem.pemEncoded(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a Base64-decoded byte array of {@code content}, using
|
||||
* {@link Base64#getMimeDecoder()}.
|
||||
*
|
||||
* @return a decoded byte array
|
||||
* @throws IllegalArgumentException if decoding fails
|
||||
*/
|
||||
final public byte[] decode() {
|
||||
return Base64.getMimeDecoder().decode(content);
|
||||
}
|
||||
}
|
||||
@ -27,6 +27,7 @@ package java.security;
|
||||
|
||||
import jdk.internal.javac.PreviewFeature;
|
||||
|
||||
import jdk.internal.ref.CleanerFactory;
|
||||
import sun.security.pkcs.PKCS8Key;
|
||||
import sun.security.rsa.RSAPrivateCrtKeyImpl;
|
||||
import sun.security.util.KeyUtil;
|
||||
@ -35,6 +36,7 @@ import sun.security.util.Pem;
|
||||
import javax.crypto.EncryptedPrivateKeyInfo;
|
||||
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.*;
|
||||
@ -43,96 +45,105 @@ import java.util.Objects;
|
||||
|
||||
/**
|
||||
* {@code PEMDecoder} implements a decoder for Privacy-Enhanced Mail (PEM) data.
|
||||
* PEM is a textual encoding used to store and transfer security
|
||||
* 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-formatted 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 binary encoding enclosed by a type-identifying header
|
||||
* and footer.
|
||||
*
|
||||
* <p> The {@linkplain #decode(String)} and {@linkplain #decode(InputStream)}
|
||||
* methods return an instance of a class that matches the data
|
||||
* type and implements {@link DEREncodable}.
|
||||
*
|
||||
* <p> The following lists the supported PEM types and the {@code DEREncodable}
|
||||
* types that each are decoded as:
|
||||
* <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:
|
||||
* <ul>
|
||||
* <li>CERTIFICATE : {@code X509Certificate}</li>
|
||||
* <li>X509 CRL : {@code X509CRL}</li>
|
||||
* <li>PUBLIC KEY : {@code PublicKey}</li>
|
||||
* <li>PUBLIC KEY : {@code X509EncodedKeySpec} (Only supported when passed as
|
||||
* a {@code Class} parameter)</li>
|
||||
* <li>PRIVATE KEY : {@code PrivateKey}</li>
|
||||
* <li>PRIVATE KEY : {@code PKCS8EncodedKeySpec} (Only supported when passed
|
||||
* as a {@code Class} parameter)</li>
|
||||
* <li>PRIVATE KEY : {@code KeyPair} (if the encoding also contains a
|
||||
* public key)</li>
|
||||
* <li>ENCRYPTED PRIVATE KEY : {@code EncryptedPrivateKeyInfo} </li>
|
||||
* <li>ENCRYPTED PRIVATE KEY : {@code PrivateKey} (if configured with
|
||||
* Decryption)</li>
|
||||
* <li>Other types : {@code PEMRecord} </li>
|
||||
* <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>
|
||||
* </ul>
|
||||
* When used with a {@code PEMDecoder} instance configured for decryption:
|
||||
* <ul>
|
||||
* <li>ENCRYPTED PRIVATE KEY : {@link PrivateKey} or {@link KeyPair}
|
||||
* (if the encoding contains a public key)</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p> The {@code PublicKey} and {@code PrivateKey} types, an algorithm specific
|
||||
* subclass is returned if the underlying algorithm is supported. For example an
|
||||
* ECPublicKey and ECPrivateKey for Elliptic Curve keys.
|
||||
* <p> 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
|
||||
* {@link PEMRecord}.
|
||||
* {@code PEM} object.
|
||||
*
|
||||
* <p> The {@linkplain #decode(String, Class)} and
|
||||
* {@linkplain #decode(InputStream, Class)} methods take a class parameter
|
||||
* which determines the type of {@code DEREncodable} that is returned. These
|
||||
* methods are useful when extracting or changing the return class.
|
||||
* For example, if the PEM contains both public and private keys, the
|
||||
* class parameter can specify which to return. Use
|
||||
* {@code PrivateKey.class} to return only the private key.
|
||||
* If the class parameter is set to {@code X509EncodedKeySpec.class}, the
|
||||
* public key will be returned in that format. Any type of PEM data can be
|
||||
* decoded into a {@code PEMRecord} by specifying {@code PEMRecord.class}.
|
||||
* If the class parameter doesn't match the PEM content, a
|
||||
* {@linkplain ClassCastException} will be thrown.
|
||||
* <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.
|
||||
*
|
||||
* <p> In addition to the types listed above, these methods support the
|
||||
* following PEM types and {@code DEREncodable} 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>
|
||||
* </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>
|
||||
* </ul>
|
||||
*
|
||||
* <p> A new {@code PEMDecoder} instance is created when configured
|
||||
* with {@linkplain #withFactory(Provider)} and/or
|
||||
* {@linkplain #withDecryption(char[])}. {@linkplain #withFactory(Provider)}
|
||||
* configures the decoder to use only {@linkplain KeyFactory} and
|
||||
* {@linkplain CertificateFactory} instances from the given {@code Provider}.
|
||||
* {@linkplain #withDecryption(char[])} configures the decoder to decrypt all
|
||||
* encrypted private key PEM data using the given password.
|
||||
* Configuring an instance for decryption does not prevent decoding with
|
||||
* unencrypted PEM. Any encrypted PEM that fails decryption
|
||||
* will throw a {@link RuntimeException}. When an encrypted private key PEM is
|
||||
* used with a decoder not configured for decryption, an
|
||||
* {@link EncryptedPrivateKeyInfo} object is returned.
|
||||
* 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
|
||||
* 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.
|
||||
*
|
||||
* <p>This class is immutable and thread-safe.
|
||||
* <p> This class is immutable and thread-safe.
|
||||
*
|
||||
* <p> Here is an example of decoding a {@code PrivateKey} object:
|
||||
* <p> Example: decode a private key:
|
||||
* {@snippet lang = java:
|
||||
* PEMDecoder pd = PEMDecoder.of();
|
||||
* PrivateKey priKey = pd.decode(priKeyPEM, PrivateKey.class);
|
||||
* }
|
||||
*
|
||||
* <p> Here is an example of a {@code PEMDecoder} configured with decryption
|
||||
* and a factory provider:
|
||||
* <p> Example: configure decryption and a factory provider:
|
||||
* {@snippet lang = java:
|
||||
* PEMDecoder pd = PEMDecoder.of().withDecryption(password).
|
||||
* withFactory(provider);
|
||||
* byte[] pemData = pd.decode(privKey);
|
||||
* withFactory(provider);
|
||||
* DEREncodable pemData = pd.decode(privKeyPEM);
|
||||
* }
|
||||
*
|
||||
* @implNote An implementation may support other PEM types and
|
||||
* {@code DEREncodable} objects. This implementation additionally supports
|
||||
* the following PEM types: {@code X509 CERTIFICATE},
|
||||
* {@code X.509 CERTIFICATE}, {@code CRL}, and {@code RSA PRIVATE KEY}.
|
||||
* @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.
|
||||
*
|
||||
* @see PEMEncoder
|
||||
* @see PEMRecord
|
||||
* @see PEM
|
||||
* @see EncryptedPrivateKeyInfo
|
||||
*
|
||||
* @spec https://www.rfc-editor.org/info/rfc1421
|
||||
* RFC 1421: Privacy Enhancement for Internet Electronic Mail
|
||||
* @spec https://www.rfc-editor.org/info/rfc5958
|
||||
* RFC 5958: Asymmetric Key Packages
|
||||
* @spec https://www.rfc-editor.org/info/rfc7468
|
||||
* RFC 7468: Textual Encodings of PKIX, PKCS, and CMS Structures
|
||||
*
|
||||
@ -142,7 +153,7 @@ import java.util.Objects;
|
||||
@PreviewFeature(feature = PreviewFeature.Feature.PEM_API)
|
||||
public final class PEMDecoder {
|
||||
private final Provider factory;
|
||||
private final PBEKeySpec password;
|
||||
private final PBEKeySpec keySpec;
|
||||
|
||||
// Singleton instance for PEMDecoder
|
||||
private final static PEMDecoder PEM_DECODER = new PEMDecoder(null, null);
|
||||
@ -154,8 +165,12 @@ public final class PEMDecoder {
|
||||
* decryption
|
||||
*/
|
||||
private PEMDecoder(Provider withFactory, PBEKeySpec withPassword) {
|
||||
password = withPassword;
|
||||
keySpec = withPassword;
|
||||
factory = withFactory;
|
||||
if (withPassword != null) {
|
||||
final var k = this.keySpec;
|
||||
CleanerFactory.cleaner().register(this, k::clearPassword);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -172,7 +187,7 @@ public final class PEMDecoder {
|
||||
* header and footer and proceed with decoding the base64 for the
|
||||
* appropriate type.
|
||||
*/
|
||||
private DEREncodable decode(PEMRecord pem) {
|
||||
private DEREncodable decode(PEM pem) {
|
||||
Base64.Decoder decoder = Base64.getMimeDecoder();
|
||||
|
||||
try {
|
||||
@ -185,41 +200,57 @@ public final class PEMDecoder {
|
||||
generatePublic(spec);
|
||||
}
|
||||
case Pem.PRIVATE_KEY -> {
|
||||
PKCS8Key p8key = new PKCS8Key(decoder.decode(pem.content()));
|
||||
String algo = p8key.getAlgorithm();
|
||||
KeyFactory kf = getKeyFactory(algo);
|
||||
DEREncodable d = kf.generatePrivate(
|
||||
new PKCS8EncodedKeySpec(p8key.getEncoded(), algo));
|
||||
DEREncodable d;
|
||||
PKCS8Key p8key = null;
|
||||
PKCS8EncodedKeySpec p8spec = null;
|
||||
byte[] encoding = decoder.decode(pem.content());
|
||||
|
||||
// Look for a public key inside the pkcs8 encoding.
|
||||
if (p8key.getPubKeyEncoded() != null) {
|
||||
// Check if this is a OneAsymmetricKey encoding
|
||||
X509EncodedKeySpec spec = new X509EncodedKeySpec(
|
||||
p8key.getPubKeyEncoded(), algo);
|
||||
yield new KeyPair(getKeyFactory(algo).
|
||||
generatePublic(spec), (PrivateKey) d);
|
||||
try {
|
||||
p8key = new PKCS8Key(encoding);
|
||||
String algo = p8key.getAlgorithm();
|
||||
KeyFactory kf = getKeyFactory(algo);
|
||||
p8spec = new PKCS8EncodedKeySpec(encoding, algo);
|
||||
d = kf.generatePrivate(p8spec);
|
||||
|
||||
} else if (d instanceof PKCS8Key p8 &&
|
||||
p8.getPubKeyEncoded() != null) {
|
||||
// If the KeyFactory decoded an algorithm-specific
|
||||
// encodings, look for the public key again. This
|
||||
// happens with EC and SEC1-v2 encoding
|
||||
X509EncodedKeySpec spec = new X509EncodedKeySpec(
|
||||
p8.getPubKeyEncoded(), algo);
|
||||
yield new KeyPair(getKeyFactory(algo).
|
||||
generatePublic(spec), p8);
|
||||
} else {
|
||||
// No public key, return the private key.
|
||||
yield d;
|
||||
// Look for a public key inside the pkcs8 encoding.
|
||||
if (p8key.getPubKeyEncoded() != null) {
|
||||
// Check if this is a OneAsymmetricKey encoding
|
||||
X509EncodedKeySpec spec = new X509EncodedKeySpec(
|
||||
p8key.getPubKeyEncoded(), algo);
|
||||
yield new KeyPair(getKeyFactory(algo).
|
||||
generatePublic(spec), (PrivateKey) d);
|
||||
|
||||
} else if (d instanceof PKCS8Key p8 &&
|
||||
p8.getPubKeyEncoded() != null) {
|
||||
// If the KeyFactory decoded an algorithm-specific
|
||||
// encodings, look for the public key again.
|
||||
X509EncodedKeySpec spec = new X509EncodedKeySpec(
|
||||
p8.getPubKeyEncoded(), algo);
|
||||
yield new KeyPair(getKeyFactory(algo).
|
||||
generatePublic(spec), (PrivateKey) d);
|
||||
} else {
|
||||
// No public key, return the private key.
|
||||
yield d;
|
||||
}
|
||||
} finally {
|
||||
KeyUtil.clear(encoding, p8spec, p8key);
|
||||
}
|
||||
}
|
||||
case Pem.ENCRYPTED_PRIVATE_KEY -> {
|
||||
if (password == null) {
|
||||
yield new EncryptedPrivateKeyInfo(decoder.decode(
|
||||
pem.content()));
|
||||
byte[] p8 = null;
|
||||
byte[] encoding = null;
|
||||
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);
|
||||
} finally {
|
||||
Reference.reachabilityFence(this);
|
||||
KeyUtil.clear(encoding, p8);
|
||||
}
|
||||
yield new EncryptedPrivateKeyInfo(decoder.decode(pem.content())).
|
||||
getKey(password.getPassword());
|
||||
}
|
||||
case Pem.CERTIFICATE, Pem.X509_CERTIFICATE,
|
||||
Pem.X_509_CERTIFICATE -> {
|
||||
@ -246,28 +277,26 @@ public final class PEMDecoder {
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes and returns a {@link DEREncodable} from the given {@code String}.
|
||||
* Decodes and returns a {@code DEREncodable} from the given {@code String}.
|
||||
*
|
||||
* <p> This method reads the {@code String} until PEM data is found
|
||||
* or the end of the {@code String} is reached. If no PEM data is found,
|
||||
* an {@code IllegalArgumentException} is thrown.
|
||||
*
|
||||
* <p> This method returns a Java API cryptographic object,
|
||||
* such as a {@code PrivateKey}, if the PEM type is supported.
|
||||
* Any non-PEM data preceding the PEM header is ignored by the decoder.
|
||||
* Otherwise, a {@link PEMRecord} will be returned containing
|
||||
* the type identifier and Base64-encoded data.
|
||||
* Any non-PEM data preceding the PEM header will be stored in
|
||||
* {@code leadingData}.
|
||||
* <p> A {@code DEREncodable} will be returned that best represents the
|
||||
* decoded data. 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.
|
||||
*
|
||||
* <p> Input consumed by this method is read in as
|
||||
* {@link java.nio.charset.StandardCharsets#UTF_8 UTF-8}.
|
||||
*
|
||||
* @param str a String containing PEM data
|
||||
* @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 null
|
||||
* @throws IllegalArgumentException on error in decoding or no PEM data found
|
||||
* @throws NullPointerException when {@code str} is {@code null}
|
||||
*/
|
||||
public DEREncodable decode(String str) {
|
||||
Objects.requireNonNull(str);
|
||||
@ -281,67 +310,65 @@ public final class PEMDecoder {
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes and returns a {@link DEREncodable} from the given
|
||||
* Decodes and returns a {@code DEREncodable} from the given
|
||||
* {@code InputStream}.
|
||||
*
|
||||
* <p> This method reads from the {@code InputStream} until the end of
|
||||
* the PEM footer or the end of the stream. If an I/O error occurs,
|
||||
* 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}.
|
||||
*
|
||||
* <p> This method returns a Java API cryptographic object,
|
||||
* such as a {@code PrivateKey}, if the PEM type is supported.
|
||||
* Any non-PEM data preceding the PEM header is ignored by the decoder.
|
||||
* Otherwise, a {@link PEMRecord} will be returned containing
|
||||
* the type identifier and Base64-encoded data.
|
||||
* Any non-PEM data preceding the PEM header will be stored in
|
||||
* {@code leadingData}.
|
||||
* <p> A {@code DEREncodable} will be returned that best represents the
|
||||
* decoded data. 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.
|
||||
*
|
||||
* <p> If no PEM data is found, an {@code IllegalArgumentException} is
|
||||
* thrown.
|
||||
* <p> If no PEM data is found, an {@code EOFException} is thrown.
|
||||
*
|
||||
* @param is InputStream containing PEM data
|
||||
* @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 at the end of the {@code InputStream}
|
||||
* {@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 null
|
||||
* @throws NullPointerException when {@code is} is {@code null}
|
||||
*/
|
||||
public DEREncodable decode(InputStream is) throws IOException {
|
||||
Objects.requireNonNull(is);
|
||||
PEMRecord pem = Pem.readPEM(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 extend {@link DEREncodable}
|
||||
* and be an appropriate class for the PEM type.
|
||||
* 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 PEMRecord.class},
|
||||
* a {@linkplain PEMRecord} is returned containing the
|
||||
* type identifier and Base64 encoding. Any non-PEM data preceding
|
||||
* the PEM header will be stored in {@code leadingData}. Other
|
||||
* class parameters will not return preceding non-PEM data.
|
||||
* <p> If 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 String containing PEM data
|
||||
* @param tClass the returned object class that implements
|
||||
* {@code DEREncodable}
|
||||
* @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} is invalid for the PEM type
|
||||
* @throws NullPointerException when any input values are null
|
||||
* @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);
|
||||
@ -355,47 +382,47 @@ public final class PEMDecoder {
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes and returns the specified class for the given
|
||||
* {@link InputStream}. The class must extend {@link DEREncodable} and be
|
||||
* an appropriate class for the PEM type.
|
||||
* 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.
|
||||
*
|
||||
* <p> This method reads from the {@code InputStream} until the end of
|
||||
* the PEM footer or the end of the stream. If an I/O error occurs,
|
||||
* 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}.
|
||||
*
|
||||
* <p> If the class parameter is {@code PEMRecord.class},
|
||||
* a {@linkplain PEMRecord} is returned containing the
|
||||
* type identifier and Base64 encoding. Any non-PEM data preceding
|
||||
* the PEM header will be stored in {@code leadingData}. Other
|
||||
* class parameters will not return preceding non-PEM data.
|
||||
* <p> If 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 no PEM data is found, an {@code IllegalArgumentException} is
|
||||
* thrown.
|
||||
* <p> If no PEM data is found, an {@code EOFException} is thrown.
|
||||
*
|
||||
* @param <S> Class type parameter that extends {@code DEREncodable}.
|
||||
* @param is an InputStream containing PEM data
|
||||
* @param tClass the returned object class that implements
|
||||
* {@code DEREncodable}.
|
||||
* @param <S> class type parameter that extends {@code DEREncodable}
|
||||
* @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 at the end of the {@code InputStream}
|
||||
* {@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 ClassCastException if {@code tClass} is invalid for the PEM type
|
||||
* @throws NullPointerException when any input values are null
|
||||
* @throws ClassCastException if {@code tClass} does not represent the PEM type
|
||||
* @throws NullPointerException when any input values are {@code null}
|
||||
*
|
||||
* @see #decode(InputStream)
|
||||
* @see #decode(InputStream)
|
||||
* @see #decode(String, Class)
|
||||
*/
|
||||
public <S extends DEREncodable> S decode(InputStream is, Class<S> tClass)
|
||||
throws IOException {
|
||||
Objects.requireNonNull(is);
|
||||
Objects.requireNonNull(tClass);
|
||||
PEMRecord pem = Pem.readPEM(is);
|
||||
PEM pem = Pem.readPEM(is);
|
||||
|
||||
if (tClass.isAssignableFrom(PEMRecord.class)) {
|
||||
if (tClass.isAssignableFrom(PEM.class)) {
|
||||
return tClass.cast(pem);
|
||||
}
|
||||
DEREncodable so = decode(pem);
|
||||
@ -478,28 +505,28 @@ public final class PEMDecoder {
|
||||
|
||||
/**
|
||||
* Returns a copy of this {@code PEMDecoder} instance that uses
|
||||
* {@link KeyFactory} and {@link CertificateFactory} implementations
|
||||
* from the specified {@link Provider} to produce cryptographic objects.
|
||||
* {@code KeyFactory} and {@code CertificateFactory} implementations
|
||||
* from the specified {@code Provider} to produce cryptographic objects.
|
||||
* Any errors using the {@code Provider} will occur during decoding.
|
||||
*
|
||||
* @param provider the factory provider
|
||||
* @return a new PEMEncoder instance configured to the {@code Provider}.
|
||||
* @throws NullPointerException if {@code provider} is null
|
||||
* @return a new {@code PEMDecoder} instance configured with the {@code Provider}
|
||||
* @throws NullPointerException if {@code provider} is {@code null}
|
||||
*/
|
||||
public PEMDecoder withFactory(Provider provider) {
|
||||
Objects.requireNonNull(provider);
|
||||
return new PEMDecoder(provider, password);
|
||||
return new PEMDecoder(provider, keySpec);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy of this {@code PEMDecoder} that decodes and decrypts
|
||||
* encrypted private keys using the specified password.
|
||||
* Non-encrypted PEM can still be decoded from this instance.
|
||||
* Non-encrypted PEM can also be decoded from this instance.
|
||||
*
|
||||
* @param password the password to decrypt encrypted PEM data. This array
|
||||
* @param password the password to decrypt the encrypted PEM data. This array
|
||||
* is cloned and stored in the new instance.
|
||||
* @return a new PEMEncoder instance configured for decryption
|
||||
* @throws NullPointerException if {@code password} is null
|
||||
* @return a new {@code PEMDecoder} instance configured for decryption
|
||||
* @throws NullPointerException if {@code password} is {@code null}
|
||||
*/
|
||||
public PEMDecoder withDecryption(char[] password) {
|
||||
Objects.requireNonNull(password);
|
||||
|
||||
@ -27,10 +27,8 @@ package java.security;
|
||||
|
||||
import jdk.internal.javac.PreviewFeature;
|
||||
import sun.security.pkcs.PKCS8Key;
|
||||
import sun.security.util.DerOutputStream;
|
||||
import sun.security.util.DerValue;
|
||||
import sun.security.util.KeyUtil;
|
||||
import sun.security.util.Pem;
|
||||
import sun.security.x509.AlgorithmId;
|
||||
|
||||
import javax.crypto.*;
|
||||
import javax.crypto.spec.PBEKeySpec;
|
||||
@ -41,83 +39,84 @@ import java.security.spec.AlgorithmParameterSpec;
|
||||
import java.security.spec.PKCS8EncodedKeySpec;
|
||||
import java.security.spec.X509EncodedKeySpec;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
/**
|
||||
* {@code PEMEncoder} implements an encoder for Privacy-Enhanced Mail (PEM)
|
||||
* data. PEM is a textual encoding used to store and transfer security
|
||||
* data. PEM is a textual encoding used to store and transfer cryptographic
|
||||
* objects, such as asymmetric keys, certificates, and certificate revocation
|
||||
* lists (CRL). It is defined in RFC 1421 and RFC 7468. PEM consists of a
|
||||
* Base64-formatted binary encoding enclosed by a type-identifying header
|
||||
* 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
|
||||
* and footer.
|
||||
*
|
||||
* <p> Encoding may be performed on Java API cryptographic objects that
|
||||
* <p> Encoding can be performed on cryptographic objects that
|
||||
* implement {@link DEREncodable}. The {@link #encode(DEREncodable)}
|
||||
* and {@link #encodeToString(DEREncodable)} methods encode a DEREncodable
|
||||
* into PEM and return the data in a byte array or String.
|
||||
* and {@link #encodeToString(DEREncodable)} methods encode a {@code DEREncodable}
|
||||
* 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 {@linkplain #withEncryption(char[])} method,
|
||||
* {@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 {@code EncryptedKeyInfo} object can be encoded
|
||||
* 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.
|
||||
*
|
||||
* <p> PKCS #8 2.0 defines the ASN.1 OneAsymmetricKey structure, which may
|
||||
* <p> PKCS #8 v2.0 defines the ASN.1 OneAsymmetricKey structure, which may
|
||||
* contain both private and public keys.
|
||||
* {@link KeyPair} objects passed to the {@code encode} or
|
||||
* {@code KeyPair} objects passed to the {@code encode} or
|
||||
* {@code encodeToString} methods are encoded as a
|
||||
* OneAsymmetricKey structure using the "PRIVATE KEY" type.
|
||||
*
|
||||
* <p> When encoding a {@link PEMRecord}, the API surrounds the
|
||||
* {@linkplain PEMRecord#content()} with the PEM header and footer
|
||||
* from {@linkplain PEMRecord#type()}. {@linkplain PEMRecord#leadingData()} is
|
||||
* not included in the encoding. {@code PEMRecord} will not perform
|
||||
* validity checks on the data.
|
||||
*
|
||||
* <p>The following lists the supported {@code DEREncodable} classes and
|
||||
* the PEM types that each are encoded as:
|
||||
* <p> When encoding a {@link PEM} object, the API surrounds
|
||||
* {@link PEM#content()} with a PEM header and footer based on
|
||||
* {@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:
|
||||
* <ul>
|
||||
* <li>{@code X509Certificate} : CERTIFICATE</li>
|
||||
* <li>{@code X509CRL} : X509 CRL</li>
|
||||
* <li>{@code PublicKey}: PUBLIC KEY</li>
|
||||
* <li>{@code PrivateKey} : PRIVATE KEY</li>
|
||||
* <li>{@code PrivateKey} (if configured with encryption):
|
||||
* ENCRYPTED PRIVATE KEY</li>
|
||||
* <li>{@code EncryptedPrivateKeyInfo} : ENCRYPTED PRIVATE KEY</li>
|
||||
* <li>{@code KeyPair} : PRIVATE KEY</li>
|
||||
* <li>{@code X509EncodedKeySpec} : PUBLIC KEY</li>
|
||||
* <li>{@code PKCS8EncodedKeySpec} : PRIVATE KEY</li>
|
||||
* <li>{@code PEMRecord} : {@code PEMRecord.type()}</li>
|
||||
* </ul>
|
||||
* <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>
|
||||
* </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>
|
||||
* </ul>
|
||||
*
|
||||
* <p> This class is immutable and thread-safe.
|
||||
*
|
||||
* <p> Here is an example of encoding a {@code PrivateKey} object:
|
||||
* <p> Example: encode a private key:
|
||||
* {@snippet lang = java:
|
||||
* PEMEncoder pe = PEMEncoder.of();
|
||||
* byte[] pemData = pe.encode(privKey);
|
||||
* }
|
||||
*
|
||||
* <p> Here is an example that encrypts and encodes a private key using the
|
||||
* specified password:
|
||||
* <p> Example: encrypt and encode a private key using a password:
|
||||
* {@snippet lang = java:
|
||||
* PEMEncoder pe = PEMEncoder.of().withEncryption(password);
|
||||
* byte[] pemData = pe.encode(privKey);
|
||||
* }
|
||||
*
|
||||
* @implNote An implementation may support other PEM types and
|
||||
* {@code DEREncodable} objects.
|
||||
* @implNote Implementations may support additional PEM types.
|
||||
*
|
||||
*
|
||||
* @see PEMDecoder
|
||||
* @see PEMRecord
|
||||
* @see PEM
|
||||
* @see EncryptedPrivateKeyInfo
|
||||
*
|
||||
* @spec https://www.rfc-editor.org/info/rfc1421
|
||||
* RFC 1421: Privacy Enhancement for Internet Electronic Mail
|
||||
* @spec https://www.rfc-editor.org/info/rfc5958
|
||||
* RFC 5958: Asymmetric Key Packages
|
||||
* @spec https://www.rfc-editor.org/info/rfc7468
|
||||
* RFC 7468: Textual Encodings of PKIX, PKCS, and CMS Structures
|
||||
*
|
||||
@ -128,24 +127,26 @@ public final class PEMEncoder {
|
||||
|
||||
// Singleton instance of PEMEncoder
|
||||
private static final PEMEncoder PEM_ENCODER = new PEMEncoder(null);
|
||||
|
||||
// Stores the password for an encrypted encoder that isn't setup yet.
|
||||
private PBEKeySpec keySpec;
|
||||
// Stores the key after the encoder is ready to encrypt. The prevents
|
||||
// repeated SecretKeyFactory calls if the encoder is used on multiple keys.
|
||||
private SecretKey key;
|
||||
// Makes SecretKeyFactory generation thread-safe.
|
||||
private final ReentrantLock lock;
|
||||
// PBE key for encryption
|
||||
private final Key key;
|
||||
|
||||
/**
|
||||
* Instantiate a {@code PEMEncoder} for Encrypted Private Keys.
|
||||
*
|
||||
* @param pbe contains the password spec used for encryption.
|
||||
* Create an encrypted {@code PEMEncoder} instance.
|
||||
*/
|
||||
private PEMEncoder(PBEKeySpec pbe) {
|
||||
keySpec = pbe;
|
||||
key = null;
|
||||
lock = new ReentrantLock();
|
||||
private PEMEncoder(PBEKeySpec keySpec) {
|
||||
if (keySpec != null) {
|
||||
try {
|
||||
key = SecretKeyFactory.getInstance(Pem.DEFAULT_ALGO).
|
||||
generateSecret(keySpec);
|
||||
} catch (GeneralSecurityException e) {
|
||||
throw new IllegalArgumentException("Operation failed: " +
|
||||
"unable to generate key or locate a valid algorithm. " +
|
||||
"Check the jdk.epkcs8.defaultAlgorithm security " +
|
||||
"property for a valid configuration.", e);
|
||||
}
|
||||
} else {
|
||||
key = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -158,70 +159,90 @@ public final class PEMEncoder {
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes the specified {@code DEREncodable} and returns a PEM encoded
|
||||
* Encodes the specified {@code DEREncodable} and returns a PEM-encoded
|
||||
* string.
|
||||
*
|
||||
* @param de the {@code DEREncodable} to be encoded
|
||||
* @return a {@code String} containing the PEM encoded data
|
||||
* @throws IllegalArgumentException if the {@code DEREncodable} cannot be
|
||||
* encoded
|
||||
* @return a {@code String} containing the PEM-encoded data
|
||||
* @throws IllegalArgumentException if the {@code DEREncodable} cannot be encoded
|
||||
* @throws NullPointerException if {@code de} is {@code null}
|
||||
* @see #withEncryption(char[])
|
||||
*/
|
||||
public String encodeToString(DEREncodable de) {
|
||||
Objects.requireNonNull(de);
|
||||
return switch (de) {
|
||||
case PublicKey pu -> buildKey(null, pu.getEncoded());
|
||||
case PrivateKey pr -> buildKey(pr.getEncoded(), null);
|
||||
case KeyPair kp -> {
|
||||
if (kp.getPublic() == null) {
|
||||
throw new IllegalArgumentException("KeyPair does not " +
|
||||
"contain PublicKey.");
|
||||
}
|
||||
if (kp.getPrivate() == null) {
|
||||
throw new IllegalArgumentException("KeyPair does not " +
|
||||
"contain PrivateKey.");
|
||||
}
|
||||
yield buildKey(kp.getPrivate().getEncoded(),
|
||||
kp.getPublic().getEncoded());
|
||||
}
|
||||
case X509EncodedKeySpec x ->
|
||||
buildKey(null, x.getEncoded());
|
||||
case PKCS8EncodedKeySpec p ->
|
||||
buildKey(p.getEncoded(), null);
|
||||
case EncryptedPrivateKeyInfo epki -> {
|
||||
case PublicKey pu -> buildKey(pu.getEncoded(), null);
|
||||
case PrivateKey pr -> {
|
||||
byte[] encoding = pr.getEncoded();
|
||||
try {
|
||||
yield Pem.pemEncoded(Pem.ENCRYPTED_PRIVATE_KEY,
|
||||
epki.getEncoded());
|
||||
yield buildKey(null, encoding);
|
||||
} finally {
|
||||
KeyUtil.clear(encoding);
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
case X509EncodedKeySpec x -> buildKey(x.getEncoded(), null);
|
||||
case PKCS8EncodedKeySpec p -> buildKey(null, p.getEncoded());
|
||||
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);
|
||||
} catch (IOException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
} finally {
|
||||
KeyUtil.clear(encoding);
|
||||
}
|
||||
}
|
||||
case X509Certificate c -> {
|
||||
if (key != null) {
|
||||
throw new IllegalArgumentException("Certificates " +
|
||||
"cannot be encrypted");
|
||||
}
|
||||
try {
|
||||
if (isEncrypted()) {
|
||||
throw new IllegalArgumentException("Certificates " +
|
||||
"cannot be encrypted");
|
||||
}
|
||||
yield Pem.pemEncoded(Pem.CERTIFICATE, c.getEncoded());
|
||||
} catch (CertificateEncodingException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}
|
||||
case X509CRL crl -> {
|
||||
if (key != null) {
|
||||
throw new IllegalArgumentException("CRLs cannot be " +
|
||||
"encrypted");
|
||||
}
|
||||
try {
|
||||
if (isEncrypted()) {
|
||||
throw new IllegalArgumentException("CRLs cannot be " +
|
||||
"encrypted");
|
||||
}
|
||||
yield Pem.pemEncoded(Pem.X509_CRL, crl.getEncoded());
|
||||
} catch (CRLException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}
|
||||
case PEMRecord rec -> {
|
||||
if (isEncrypted()) {
|
||||
throw new IllegalArgumentException("PEMRecord cannot be " +
|
||||
case PEM rec -> {
|
||||
if (key != null) {
|
||||
throw new IllegalArgumentException("PEM cannot be " +
|
||||
"encrypted");
|
||||
}
|
||||
yield Pem.pemEncoded(rec);
|
||||
@ -233,13 +254,12 @@ public final class PEMEncoder {
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes the specified {@code DEREncodable} and returns the PEM encoding
|
||||
* in a byte array.
|
||||
* 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
|
||||
* @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[])
|
||||
*/
|
||||
@ -248,136 +268,95 @@ public final class PEMEncoder {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new {@code PEMEncoder} instance configured for encryption
|
||||
* with the default algorithm and a given password.
|
||||
* Returns a copy of this PEMEncoder that encrypts and encodes
|
||||
* using the specified password and default encryption algorithm.
|
||||
*
|
||||
* <p> Only {@link PrivateKey} objects can be encrypted with this newly
|
||||
* configured instance. Encoding other {@link DEREncodable} objects will
|
||||
* <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}.
|
||||
*
|
||||
* @implNote
|
||||
* The default password-based encryption algorithm is defined
|
||||
* by the {@code jdk.epkcs8.defaultAlgorithm} security property and
|
||||
* uses the default encryption parameters of the provider that is selected.
|
||||
* For greater flexibility with encryption options and parameters, use
|
||||
* {@link EncryptedPrivateKeyInfo#encryptKey(PrivateKey, Key,
|
||||
* @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)}.
|
||||
*
|
||||
* @param password the encryption password. The array is cloned and
|
||||
* stored in the new instance.
|
||||
* stored in the new instance.
|
||||
* @return a new {@code PEMEncoder} instance configured for encryption
|
||||
* @throws NullPointerException when password is {@code null}
|
||||
* @throws NullPointerException if password is {@code null}
|
||||
* @throws IllegalArgumentException if generating the encryption key fails
|
||||
*/
|
||||
public PEMEncoder withEncryption(char[] password) {
|
||||
// PBEKeySpec clones the password
|
||||
Objects.requireNonNull(password, "password cannot be null.");
|
||||
return new PEMEncoder(new PBEKeySpec(password));
|
||||
PBEKeySpec keySpec = new PBEKeySpec(password);
|
||||
try {
|
||||
return new PEMEncoder(keySpec);
|
||||
} finally {
|
||||
keySpec.clearPassword();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build PEM encoding.
|
||||
*
|
||||
* privateKeyEncoding will be zeroed when the method returns
|
||||
*/
|
||||
private String buildKey(byte[] privateBytes, byte[] publicBytes) {
|
||||
DerOutputStream out = new DerOutputStream();
|
||||
Cipher cipher;
|
||||
|
||||
if (privateBytes == null && publicBytes == null) {
|
||||
private String buildKey(byte[] publicEncoding, byte[] privateEncoding) {
|
||||
if (publicEncoding == null && privateEncoding == null) {
|
||||
throw new IllegalArgumentException("No encoded data given by the " +
|
||||
"DEREncodable.");
|
||||
}
|
||||
|
||||
// If `keySpec` is non-null, then `key` hasn't been established.
|
||||
// Setting a `key` prevents repeated key generation operations.
|
||||
// withEncryption() is a configuration method and cannot throw an
|
||||
// exception; therefore generation is delayed.
|
||||
if (keySpec != null) {
|
||||
// For thread safety
|
||||
lock.lock();
|
||||
if (key == null) {
|
||||
try {
|
||||
key = SecretKeyFactory.getInstance(Pem.DEFAULT_ALGO).
|
||||
generateSecret(keySpec);
|
||||
keySpec.clearPassword();
|
||||
keySpec = null;
|
||||
} catch (GeneralSecurityException e) {
|
||||
throw new IllegalArgumentException("Security property " +
|
||||
"\"jdk.epkcs8.defaultAlgorithm\" may not specify a " +
|
||||
"valid algorithm. Operation cannot be performed.", e);
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
} else {
|
||||
lock.unlock();
|
||||
}
|
||||
if (publicEncoding != null && publicEncoding.length == 0) {
|
||||
throw new IllegalArgumentException("Public key has no " +
|
||||
"encoding");
|
||||
}
|
||||
|
||||
// If `key` is non-null, this is an encoder ready to encrypt.
|
||||
if (key != null) {
|
||||
if (privateBytes == null || publicBytes != null) {
|
||||
throw new IllegalArgumentException("Can only encrypt a " +
|
||||
"PrivateKey.");
|
||||
}
|
||||
if (privateEncoding != null && privateEncoding.length == 0) {
|
||||
throw new IllegalArgumentException("Private key has no " +
|
||||
"encoding");
|
||||
}
|
||||
|
||||
try {
|
||||
cipher = Cipher.getInstance(Pem.DEFAULT_ALGO);
|
||||
cipher.init(Cipher.ENCRYPT_MODE, key);
|
||||
} catch (GeneralSecurityException e) {
|
||||
throw new IllegalArgumentException("Security property " +
|
||||
"\"jdk.epkcs8.defaultAlgorithm\" may not specify a " +
|
||||
"valid algorithm. Operation cannot be performed.", e);
|
||||
}
|
||||
|
||||
try {
|
||||
new AlgorithmId(Pem.getPBEID(Pem.DEFAULT_ALGO),
|
||||
cipher.getParameters()).encode(out);
|
||||
out.putOctetString(cipher.doFinal(privateBytes));
|
||||
return Pem.pemEncoded(Pem.ENCRYPTED_PRIVATE_KEY,
|
||||
DerValue.wrap(DerValue.tag_Sequence, out).toByteArray());
|
||||
} catch (GeneralSecurityException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
if (key != null && privateEncoding == null) {
|
||||
throw new IllegalArgumentException("This DEREncodable cannot " +
|
||||
"be encrypted.");
|
||||
}
|
||||
|
||||
// X509 only
|
||||
if (publicBytes != null && privateBytes == null) {
|
||||
if (publicBytes.length == 0) {
|
||||
throw new IllegalArgumentException("No public key encoding " +
|
||||
"given by the DEREncodable.");
|
||||
}
|
||||
|
||||
return Pem.pemEncoded(Pem.PUBLIC_KEY, publicBytes);
|
||||
if (publicEncoding != null && privateEncoding == null) {
|
||||
return Pem.pemEncoded(Pem.PUBLIC_KEY, publicEncoding);
|
||||
}
|
||||
|
||||
// PKCS8 only
|
||||
if (publicBytes == null && privateBytes != null) {
|
||||
if (privateBytes.length == 0) {
|
||||
byte[] encoding = null;
|
||||
PKCS8EncodedKeySpec p8KeySpec = null;
|
||||
try {
|
||||
if (publicEncoding == null) {
|
||||
encoding = privateEncoding;
|
||||
} else {
|
||||
encoding = PKCS8Key.getEncoded(publicEncoding,
|
||||
privateEncoding);
|
||||
}
|
||||
if (key != null) {
|
||||
p8KeySpec = new PKCS8EncodedKeySpec(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.");
|
||||
}
|
||||
|
||||
return Pem.pemEncoded(Pem.PRIVATE_KEY, privateBytes);
|
||||
}
|
||||
|
||||
// OneAsymmetricKey
|
||||
if (privateBytes.length == 0) {
|
||||
throw new IllegalArgumentException("No private key encoding " +
|
||||
"given by the DEREncodable.");
|
||||
}
|
||||
|
||||
if (publicBytes.length == 0) {
|
||||
throw new IllegalArgumentException("No public key encoding " +
|
||||
"given by the DEREncodable.");
|
||||
}
|
||||
try {
|
||||
return Pem.pemEncoded(Pem.PRIVATE_KEY,
|
||||
PKCS8Key.getEncoded(publicBytes, privateBytes));
|
||||
return Pem.pemEncoded(
|
||||
(key == null ? Pem.PRIVATE_KEY : Pem.ENCRYPTED_PRIVATE_KEY),
|
||||
encoding);
|
||||
} catch (IOException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
throw new IllegalArgumentException("Error while encoding", e);
|
||||
} finally {
|
||||
KeyUtil.clear(encoding, p8KeySpec);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isEncrypted() {
|
||||
return (key != null || keySpec != null);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,120 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package java.security;
|
||||
|
||||
import jdk.internal.javac.PreviewFeature;
|
||||
|
||||
import sun.security.util.Pem;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* {@code PEMRecord} is a {@link DEREncodable} that represents Privacy-Enhanced
|
||||
* Mail (PEM) data by its type and Base64 form. {@link PEMDecoder} and
|
||||
* {@link PEMEncoder} use {@code PEMRecord} when representing the data as a
|
||||
* cryptographic object is not desired or the type has no
|
||||
* {@code DEREncodable}.
|
||||
*
|
||||
* <p> {@code type} and {@code content} may not be {@code null}.
|
||||
* {@code leadingData} may be null if no non-PEM data preceded PEM header
|
||||
* during decoding. {@code leadingData} may be useful for reading metadata
|
||||
* that accompanies PEM data.
|
||||
*
|
||||
* <p> No validation is performed during instantiation to ensure that
|
||||
* {@code type} conforms to {@code RFC 7468}, that {@code content} is valid
|
||||
* Base64, or that {@code content} matches the {@code type}.
|
||||
* {@code leadingData} is not defensively copied and does not return a
|
||||
* clone when {@linkplain #leadingData()} is called.
|
||||
*
|
||||
* @param type the type identifier in the PEM header without PEM syntax labels.
|
||||
* For a public key, {@code type} would be "PUBLIC KEY".
|
||||
* @param content the Base64-encoded data, excluding the PEM header and footer
|
||||
* @param leadingData any non-PEM data preceding the PEM header when decoding.
|
||||
*
|
||||
* @spec https://www.rfc-editor.org/info/rfc7468
|
||||
* RFC 7468: Textual Encodings of PKIX, PKCS, and CMS Structures
|
||||
*
|
||||
* @see PEMDecoder
|
||||
* @see PEMEncoder
|
||||
*
|
||||
* @since 25
|
||||
*/
|
||||
@PreviewFeature(feature = PreviewFeature.Feature.PEM_API)
|
||||
public record PEMRecord(String type, String content, byte[] leadingData)
|
||||
implements DEREncodable {
|
||||
|
||||
/**
|
||||
* Creates a {@code PEMRecord} instance with the given parameters.
|
||||
*
|
||||
* @param type the 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 maybe {@code null}.
|
||||
* @throws IllegalArgumentException if {@code type} is incorrectly
|
||||
* formatted.
|
||||
* @throws NullPointerException if {@code type} and/or {@code content} are
|
||||
* {@code null}.
|
||||
*/
|
||||
public PEMRecord {
|
||||
Objects.requireNonNull(type, "\"type\" cannot be null.");
|
||||
Objects.requireNonNull(content, "\"content\" cannot be null.");
|
||||
|
||||
// With no validity checking on `type`, the constructor accept anything
|
||||
// including lowercase. The onus is on the caller.
|
||||
if (type.startsWith("-") || type.startsWith("BEGIN ") ||
|
||||
type.startsWith("END ")) {
|
||||
throw new IllegalArgumentException("PEM syntax labels found. " +
|
||||
"Only the PEM type identifier is allowed");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@code PEMRecord} instance with a given {@code type} and
|
||||
* {@code content} data in String form. {@code leadingData} is set to null.
|
||||
*
|
||||
* @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} and/or {@code content} are
|
||||
* {@code null}.
|
||||
*/
|
||||
public PEMRecord(String type, String content) {
|
||||
this(type, content, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the type and Base64 encoding in PEM format. {@code leadingData}
|
||||
* is not returned by this method.
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return Pem.pemEncoded(this);
|
||||
}
|
||||
}
|
||||
@ -276,8 +276,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.
|
||||
* @exception NullPointerException if {@code cipher}
|
||||
* is {@code null}.
|
||||
* @exception NullPointerException if {@code cipher} is {@code null}.
|
||||
* @exception InvalidKeySpecException if the given cipher is
|
||||
* inappropriate for the encrypted data or the encrypted
|
||||
* data is corrupted and cannot be decrypted.
|
||||
@ -296,10 +295,9 @@ public non-sealed class EncryptedPrivateKeyInfo implements DEREncodable {
|
||||
}
|
||||
}
|
||||
|
||||
private PKCS8EncodedKeySpec getKeySpecImpl(Key decryptKey,
|
||||
Provider provider) throws NoSuchAlgorithmException,
|
||||
InvalidKeyException {
|
||||
byte[] encoded;
|
||||
// Return the decrypted encryptedData in this instance.
|
||||
private byte[] decryptData(Key decryptKey, Provider provider)
|
||||
throws NoSuchAlgorithmException, InvalidKeyException {
|
||||
Cipher c;
|
||||
try {
|
||||
if (provider == null) {
|
||||
@ -308,162 +306,157 @@ public non-sealed class EncryptedPrivateKeyInfo implements DEREncodable {
|
||||
} else {
|
||||
c = Cipher.getInstance(getAlgName(), provider);
|
||||
}
|
||||
} catch (NoSuchPaddingException e) {
|
||||
throw new NoSuchAlgorithmException(e);
|
||||
}
|
||||
try {
|
||||
c.init(Cipher.DECRYPT_MODE, decryptKey, getAlgParameters());
|
||||
encoded = c.doFinal(encryptedData);
|
||||
return pkcs8EncodingToSpec(encoded);
|
||||
return c.doFinal(encryptedData);
|
||||
} catch (GeneralSecurityException e) {
|
||||
throw new InvalidKeyException(e);
|
||||
}
|
||||
}
|
||||
|
||||
// Wrap the decrypted encryptedData in a P8EKS for getKeySpec methods.
|
||||
private PKCS8EncodedKeySpec getKeySpecImpl(Key decryptKey,
|
||||
Provider provider) throws NoSuchAlgorithmException,
|
||||
InvalidKeyException {
|
||||
byte[] encoding = null;
|
||||
try {
|
||||
encoding = decryptData(decryptKey, provider);
|
||||
return pkcs8EncodingToSpec(encoding);
|
||||
} catch (NoSuchAlgorithmException nsae) {
|
||||
// rethrow
|
||||
throw nsae;
|
||||
} catch (GeneralSecurityException | IOException ex) {
|
||||
throw new InvalidKeyException(
|
||||
"Cannot retrieve the PKCS8EncodedKeySpec", ex);
|
||||
"Cannot retrieve the PKCS8EncodedKeySpec", ex);
|
||||
} finally {
|
||||
KeyUtil.clear(encoding);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and encrypts an {@code EncryptedPrivateKeyInfo} from a given
|
||||
* {@code PrivateKey}. A valid password-based encryption (PBE) algorithm
|
||||
* Creates an {@code EncryptedPrivateKeyInfo} by encrypting the specified
|
||||
* {@code DEREncodable}. A valid password-based encryption (PBE) algorithm
|
||||
* and password must be specified.
|
||||
*
|
||||
* <p> The PBE algorithm string format details can be found in the
|
||||
* <p>The format of the PBE algorithm string is described in the
|
||||
* <a href="{@docRoot}/../specs/security/standard-names.html#cipher-algorithms">
|
||||
* Cipher section</a> of the Java Security Standard Algorithm Names
|
||||
* Cipher Algorithms</a> section of the Java Security Standard Algorithm Names
|
||||
* Specification.
|
||||
*
|
||||
* @param key the {@code PrivateKey} to be encrypted
|
||||
* @param password the password used in the PBE encryption. This array
|
||||
* will be cloned before being used.
|
||||
* @param algorithm the PBE encryption algorithm. The default algorithm
|
||||
* will be used if {@code null}. However, {@code null} is
|
||||
* not allowed when {@code params} is non-null.
|
||||
* @param params the {@code AlgorithmParameterSpec} to be used with
|
||||
* encryption. The provider default will be used if
|
||||
* {@code null}.
|
||||
* @param provider the {@code Provider} will be used for PBE
|
||||
* {@link SecretKeyFactory} generation and {@link Cipher}
|
||||
* encryption operations. The default provider list will be
|
||||
* used if {@code null}.
|
||||
* @param de the {@code DEREncodable} 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.
|
||||
* @param algorithm the PBE encryption algorithm
|
||||
* @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.
|
||||
* @return an {@code EncryptedPrivateKeyInfo}
|
||||
* @throws IllegalArgumentException on initialization errors based on the
|
||||
* arguments passed to the method
|
||||
* @throws RuntimeException on an encryption error
|
||||
* @throws NullPointerException if the key or password are {@code null}. If
|
||||
* {@code params} is non-null when {@code algorithm} is {@code null}.
|
||||
*
|
||||
* @implNote The {@code jdk.epkcs8.defaultAlgorithm} Security Property
|
||||
* defines the default encryption algorithm and the
|
||||
* {@code AlgorithmParameterSpec} are the provider's algorithm defaults.
|
||||
* @throws NullPointerException if {@code de}, {@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
|
||||
* PBE key, if {@code algorithm} or {@code params} are
|
||||
* not supported by any provider, or if an error occurs during
|
||||
* encryption.
|
||||
*
|
||||
* @since 25
|
||||
*/
|
||||
@PreviewFeature(feature = PreviewFeature.Feature.PEM_API)
|
||||
public static EncryptedPrivateKeyInfo encryptKey(PrivateKey key,
|
||||
public static EncryptedPrivateKeyInfo encrypt(DEREncodable de,
|
||||
char[] password, String algorithm, AlgorithmParameterSpec params,
|
||||
Provider provider) {
|
||||
|
||||
SecretKey skey;
|
||||
Objects.requireNonNull(key, "key cannot be null");
|
||||
Objects.requireNonNull(password, "password cannot be null.");
|
||||
PBEKeySpec keySpec = new PBEKeySpec(password);
|
||||
if (algorithm == null) {
|
||||
if (params != null) {
|
||||
throw new NullPointerException("algorithm must be specified" +
|
||||
" if params is non-null.");
|
||||
}
|
||||
algorithm = Pem.DEFAULT_ALGO;
|
||||
}
|
||||
|
||||
Objects.requireNonNull(de, "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);
|
||||
try {
|
||||
SecretKeyFactory factory;
|
||||
if (provider == null) {
|
||||
factory = SecretKeyFactory.getInstance(algorithm);
|
||||
} else {
|
||||
factory = SecretKeyFactory.getInstance(algorithm, provider);
|
||||
}
|
||||
skey = factory.generateSecret(keySpec);
|
||||
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
return encryptImpl(encoding, algorithm,
|
||||
generateSecretKey(passwd, algorithm, provider), params,
|
||||
provider, null);
|
||||
} finally {
|
||||
KeyUtil.clear(passwd, encoding);
|
||||
}
|
||||
return encryptKeyImpl(key, algorithm, skey, params, provider, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and encrypts an {@code EncryptedPrivateKeyInfo} from a given
|
||||
* {@code PrivateKey} and password. Default algorithm and parameters are
|
||||
* used.
|
||||
* Creates an {@code EncryptedPrivateKeyInfo} by encrypting the specified
|
||||
* {@code DEREncodable}. A valid password must be specified. A default
|
||||
* password-based encryption (PBE) algorithm and provider are used.
|
||||
*
|
||||
* @param key the {@code PrivateKey} to be encrypted
|
||||
* @param password the password used in the PBE encryption. This array
|
||||
* will be cloned before being used.
|
||||
* @param de the {@code DEREncodable} 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 IllegalArgumentException on initialization errors based on the
|
||||
* arguments passed to the method
|
||||
* @throws RuntimeException on an encryption error
|
||||
* @throws NullPointerException when the {@code key} or {@code password}
|
||||
* is {@code null}
|
||||
* @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
|
||||
*
|
||||
* @implNote The {@code jdk.epkcs8.defaultAlgorithm} Security Property
|
||||
* defines the default encryption algorithm and the
|
||||
* {@code AlgorithmParameterSpec} are the provider's algorithm defaults.
|
||||
* @implNote The {@code jdk.epkcs8.defaultAlgorithm} security property
|
||||
* defines the default encryption algorithm. The {@code AlgorithmParameterSpec}
|
||||
* defaults are determined by the provider.
|
||||
*
|
||||
* @since 25
|
||||
*/
|
||||
@PreviewFeature(feature = PreviewFeature.Feature.PEM_API)
|
||||
public static EncryptedPrivateKeyInfo encryptKey(PrivateKey key,
|
||||
public static EncryptedPrivateKeyInfo encrypt(DEREncodable de,
|
||||
char[] password) {
|
||||
return encryptKey(key, password, Pem.DEFAULT_ALGO, null, null);
|
||||
return encrypt(de, password, Pem.DEFAULT_ALGO, null,
|
||||
null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and encrypts an {@code EncryptedPrivateKeyInfo} from the given
|
||||
* {@link PrivateKey} using the {@code encKey} and given parameters.
|
||||
* Creates an {@code EncryptedPrivateKeyInfo} by encrypting the specified
|
||||
* {@code DEREncodable}. A valid encryption algorithm and {@code Key} must
|
||||
* be specified.
|
||||
*
|
||||
* @param key the {@code PrivateKey} to be encrypted
|
||||
* @param encKey the password-based encryption (PBE) {@code Key} used to
|
||||
* encrypt {@code key}.
|
||||
* @param algorithm the PBE encryption algorithm. The default algorithm is
|
||||
* will be used if {@code null}; however, {@code null} is
|
||||
* not allowed when {@code params} is non-null.
|
||||
* @param params the {@code AlgorithmParameterSpec} to be used with
|
||||
* encryption. The provider list default will be used if
|
||||
* {@code null}.
|
||||
* @param random the {@code SecureRandom} instance used during
|
||||
* encryption. The default will be used if {@code null}.
|
||||
* @param provider the {@code Provider} is used for {@link Cipher}
|
||||
* encryption operation. The default provider list will be
|
||||
* used if {@code null}.
|
||||
* <p>The format of the algorithm string is described in the
|
||||
* <a href="{@docRoot}/../specs/security/standard-names.html#cipher-algorithms">
|
||||
* Cipher Algorithms</a> section of the Java Security Standard Algorithm Names
|
||||
* Specification.
|
||||
*
|
||||
* @param de the {@code DEREncodable} 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.
|
||||
* @return an {@code EncryptedPrivateKeyInfo}
|
||||
* @throws IllegalArgumentException on initialization errors based on the
|
||||
* arguments passed to the method
|
||||
* @throws RuntimeException on an encryption error
|
||||
* @throws NullPointerException if the {@code key} or {@code encKey} are
|
||||
* {@code null}. If {@code params} is non-null, {@code algorithm} cannot be
|
||||
* {@code null}.
|
||||
*
|
||||
* @implNote The {@code jdk.epkcs8.defaultAlgorithm} Security Property
|
||||
* defines the default encryption algorithm and the
|
||||
* {@code AlgorithmParameterSpec} are the provider's algorithm defaults.
|
||||
* @throws NullPointerException if {@code de}, {@code encryptKey}, or
|
||||
* {@code algorithm} is {@code null}
|
||||
* @throws IllegalArgumentException if {@code de} is an unsupported
|
||||
* {@code DEREncodable}, 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 25
|
||||
*/
|
||||
@PreviewFeature(feature = PreviewFeature.Feature.PEM_API)
|
||||
public static EncryptedPrivateKeyInfo encryptKey(PrivateKey key, Key encKey,
|
||||
String algorithm, AlgorithmParameterSpec params, Provider provider,
|
||||
SecureRandom random) {
|
||||
public static EncryptedPrivateKeyInfo encrypt(DEREncodable de,
|
||||
Key encryptKey, String algorithm, AlgorithmParameterSpec params,
|
||||
Provider provider, SecureRandom random) {
|
||||
|
||||
Objects.requireNonNull(key);
|
||||
Objects.requireNonNull(encKey);
|
||||
if (algorithm == null) {
|
||||
if (params != null) {
|
||||
throw new NullPointerException("algorithm must be specified " +
|
||||
"if params is non-null.");
|
||||
}
|
||||
algorithm = Pem.DEFAULT_ALGO;
|
||||
}
|
||||
return encryptKeyImpl(key, algorithm, encKey, params, provider, random);
|
||||
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,
|
||||
params, provider, random);
|
||||
}
|
||||
|
||||
private static EncryptedPrivateKeyInfo encryptKeyImpl(PrivateKey key,
|
||||
private static EncryptedPrivateKeyInfo encryptImpl(byte[] encoded,
|
||||
String algorithm, Key encryptKey, AlgorithmParameterSpec params,
|
||||
Provider provider, SecureRandom random) {
|
||||
AlgorithmId algId;
|
||||
@ -481,17 +474,26 @@ public non-sealed class EncryptedPrivateKeyInfo implements DEREncodable {
|
||||
c = Cipher.getInstance(algorithm, provider);
|
||||
}
|
||||
c.init(Cipher.ENCRYPT_MODE, encryptKey, params, random);
|
||||
encryptedData = c.doFinal(key.getEncoded());
|
||||
algId = new AlgorithmId(Pem.getPBEID(algorithm), c.getParameters());
|
||||
encryptedData = c.doFinal(encoded);
|
||||
try {
|
||||
// Use shared PEM method for very likely case the algorithm is PBE.
|
||||
algId = new AlgorithmId(Pem.getPBEID(algorithm), c.getParameters());
|
||||
} catch (IllegalArgumentException e) {
|
||||
// For the unlikely case a non-PBE cipher is used, get the OID.
|
||||
algId = new AlgorithmId(AlgorithmId.get(algorithm).getOID(),
|
||||
c.getParameters());
|
||||
}
|
||||
out = new DerOutputStream();
|
||||
algId.encode(out);
|
||||
out.putOctetString(encryptedData);
|
||||
} catch (InvalidAlgorithmParameterException | NoSuchAlgorithmException |
|
||||
NoSuchPaddingException e) {
|
||||
IllegalStateException | NoSuchPaddingException |
|
||||
IllegalBlockSizeException | InvalidKeyException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
} catch (IllegalBlockSizeException | BadPaddingException |
|
||||
InvalidKeyException e) {
|
||||
throw new RuntimeException(e);
|
||||
} catch (BadPaddingException e) {
|
||||
throw new AssertionError(e);
|
||||
} finally {
|
||||
KeyUtil.clear(encoded);
|
||||
}
|
||||
return new EncryptedPrivateKeyInfo(
|
||||
DerValue.wrap(DerValue.tag_Sequence, out).toByteArray(),
|
||||
@ -499,63 +501,129 @@ public non-sealed class EncryptedPrivateKeyInfo implements DEREncodable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the enclosed {@code PrivateKey} object from the encrypted data
|
||||
* and return it.
|
||||
* Extracts and returns the enclosed {@code PrivateKey} using the
|
||||
* specified password.
|
||||
*
|
||||
* @param password the password used in the PBE encryption. This array
|
||||
* will be cloned before being used.
|
||||
* @return a {@code PrivateKey}
|
||||
* @throws GeneralSecurityException if an error occurs parsing or
|
||||
* decrypting the encrypted data, or producing the key object.
|
||||
* @throws NullPointerException if {@code password} is null
|
||||
* @param password the password used for PBE decryption. The array is cloned
|
||||
* before use.
|
||||
* @return the decrypted {@code PrivateKey}
|
||||
* @throws NullPointerException if {@code password} 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
|
||||
*/
|
||||
@PreviewFeature(feature = PreviewFeature.Feature.PEM_API)
|
||||
public PrivateKey getKey(char[] password) throws GeneralSecurityException {
|
||||
SecretKeyFactory skf;
|
||||
PKCS8EncodedKeySpec p8KeySpec;
|
||||
Objects.requireNonNull(password, "password cannot be null");
|
||||
public PrivateKey getKey(char[] password)
|
||||
throws NoSuchAlgorithmException, InvalidKeyException {
|
||||
Objects.requireNonNull(password, "a password must be specified.");
|
||||
PBEKeySpec keySpec = new PBEKeySpec(password);
|
||||
skf = SecretKeyFactory.getInstance(getAlgName());
|
||||
p8KeySpec = getKeySpec(skf.generateSecret(keySpec));
|
||||
|
||||
return PKCS8Key.parseKey(p8KeySpec.getEncoded());
|
||||
try {
|
||||
return PKCS8Key.parseKey(Pem.decryptEncoding(this, keySpec), null);
|
||||
} finally {
|
||||
keySpec.clearPassword();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the enclosed {@code PrivateKey} object from the encrypted data
|
||||
* and return it.
|
||||
* Extracts and returns the enclosed {@code PrivateKey} using the specified
|
||||
* decryption key and provider.
|
||||
*
|
||||
* @param decryptKey the decryption key and cannot be {@code null}
|
||||
* @param provider the {@code Provider} used for Cipher decryption and
|
||||
* {@code PrivateKey} generation. A {@code null} value will
|
||||
* use the default provider configuration.
|
||||
* @return a {@code PrivateKey}
|
||||
* @throws GeneralSecurityException if an error occurs parsing or
|
||||
* decrypting the encrypted data, or producing the key object.
|
||||
* @throws NullPointerException if {@code decryptKey} is null
|
||||
* @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.
|
||||
* @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
|
||||
*/
|
||||
@PreviewFeature(feature = PreviewFeature.Feature.PEM_API)
|
||||
public PrivateKey getKey(Key decryptKey, Provider provider)
|
||||
throws GeneralSecurityException {
|
||||
Objects.requireNonNull(decryptKey,"decryptKey cannot be null.");
|
||||
PKCS8EncodedKeySpec p = getKeySpecImpl(decryptKey, provider);
|
||||
throws NoSuchAlgorithmException, InvalidKeyException {
|
||||
Objects.requireNonNull(decryptKey,"a decryptKey must be specified.");
|
||||
byte[] encoding = null;
|
||||
try {
|
||||
if (provider == null) {
|
||||
return KeyFactory.getInstance(
|
||||
KeyUtil.getAlgorithm(p.getEncoded())).
|
||||
generatePrivate(p);
|
||||
}
|
||||
return KeyFactory.getInstance(KeyUtil.getAlgorithm(p.getEncoded()),
|
||||
provider).generatePrivate(p);
|
||||
} catch (IOException e) {
|
||||
throw new GeneralSecurityException(e);
|
||||
encoding = decryptData(decryptKey, provider);
|
||||
return PKCS8Key.parseKey(encoding, provider);
|
||||
} finally {
|
||||
KeyUtil.clear(encoding);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts and returns the enclosed {@code KeyPair} using the specified
|
||||
* password. If the encoded data does not contain both a public and private
|
||||
* key, an {@code InvalidKeyException} is thrown.
|
||||
*
|
||||
* @param password the password used for PBE decryption. The array is cloned
|
||||
* before use.
|
||||
* @return a decrypted {@code KeyPair}
|
||||
* @throws NullPointerException if {@code password} 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
|
||||
*/
|
||||
@PreviewFeature(feature = PreviewFeature.Feature.PEM_API)
|
||||
public KeyPair getKeyPair(char[] password)
|
||||
throws NoSuchAlgorithmException, InvalidKeyException {
|
||||
Objects.requireNonNull(password, "a password must be specified.");
|
||||
|
||||
PBEKeySpec keySpec = new PBEKeySpec(password);
|
||||
DEREncodable d;
|
||||
try {
|
||||
d = Pem.toDEREncodable(Pem.decryptEncoding(this, keySpec), true, null);
|
||||
} finally {
|
||||
keySpec.clearPassword();
|
||||
}
|
||||
return switch (d) {
|
||||
case KeyPair kp -> kp;
|
||||
case PrivateKey ignored -> throw new InvalidKeyException(
|
||||
"This encoding does not contain a public key.");
|
||||
default -> throw new InvalidKeyException(
|
||||
"Invalid class returned " + d.getClass().getName());
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts and returns the enclosed {@code KeyPair} using the specified
|
||||
* decryption key and provider. 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.
|
||||
* @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
|
||||
*/
|
||||
@PreviewFeature(feature = PreviewFeature.Feature.PEM_API)
|
||||
public KeyPair getKeyPair(Key decryptKey, Provider provider)
|
||||
throws NoSuchAlgorithmException, InvalidKeyException {
|
||||
Objects.requireNonNull(decryptKey,"a decryptKey must be specified.");
|
||||
|
||||
DEREncodable d = Pem.toDEREncodable(
|
||||
decryptData(decryptKey, provider),true, provider);
|
||||
return switch (d) {
|
||||
case KeyPair kp -> kp;
|
||||
case PrivateKey ignored -> throw new InvalidKeyException(
|
||||
"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.
|
||||
@ -585,7 +653,7 @@ public non-sealed class EncryptedPrivateKeyInfo implements DEREncodable {
|
||||
* @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
|
||||
* @exception NullPointerException if {@code decryptKey}
|
||||
* or {@code providerName} is {@code null}.
|
||||
* @exception NoSuchProviderException if no provider
|
||||
@ -670,17 +738,48 @@ public non-sealed class EncryptedPrivateKeyInfo implements DEREncodable {
|
||||
return this.encoded.clone();
|
||||
}
|
||||
|
||||
private static void checkTag(DerValue val, byte tag, String valName)
|
||||
throws IOException {
|
||||
if (val.getTag() != tag) {
|
||||
throw new IOException("invalid key encoding - wrong tag for " +
|
||||
valName);
|
||||
}
|
||||
}
|
||||
|
||||
// Read the encodedKey and return a P8EKS with the algorithm specified
|
||||
private static PKCS8EncodedKeySpec pkcs8EncodingToSpec(byte[] encodedKey)
|
||||
throws IOException {
|
||||
return new PKCS8EncodedKeySpec(encodedKey,
|
||||
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);
|
||||
}
|
||||
}
|
||||
default -> throw new IllegalArgumentException(
|
||||
d.getClass().getName() + " not supported by this method");
|
||||
};
|
||||
}
|
||||
|
||||
// Generate a SecretKey from the password.
|
||||
private static SecretKey generateSecretKey(char[] password, String algorithm,
|
||||
Provider provider) {
|
||||
PBEKeySpec keySpec = new PBEKeySpec(password);
|
||||
|
||||
try {
|
||||
SecretKeyFactory factory;
|
||||
if (provider == null) {
|
||||
factory = SecretKeyFactory.getInstance(algorithm);
|
||||
} else {
|
||||
factory = SecretKeyFactory.getInstance(algorithm, provider);
|
||||
}
|
||||
return factory.generateSecret(keySpec);
|
||||
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
} finally {
|
||||
keySpec.clearPassword();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -87,7 +87,8 @@ public @interface PreviewFeature {
|
||||
STRUCTURED_CONCURRENCY,
|
||||
@JEP(number = 502, title = "Stable Values", status = "Preview")
|
||||
STABLE_VALUES,
|
||||
@JEP(number=470, title="PEM Encodings of Cryptographic Objects", status="Preview")
|
||||
@JEP(number=524, title="PEM Encodings of Cryptographic Objects",
|
||||
status="Second Preview")
|
||||
PEM_API,
|
||||
LANGUAGE_MODEL,
|
||||
/**
|
||||
|
||||
@ -26,6 +26,7 @@
|
||||
package sun.security.ec;
|
||||
|
||||
import sun.security.pkcs.PKCS8Key;
|
||||
import sun.security.util.KeyUtil;
|
||||
|
||||
import java.security.*;
|
||||
import java.security.interfaces.*;
|
||||
@ -213,11 +214,17 @@ public final class ECKeyFactory extends KeyFactorySpi {
|
||||
case ECPublicKeySpec e ->
|
||||
new ECPublicKeyImpl(e.getW(), e.getParams());
|
||||
case PKCS8EncodedKeySpec p8 -> {
|
||||
PKCS8Key p8key = new ECPrivateKeyImpl(p8.getEncoded());
|
||||
if (!p8key.hasPublicKey()) {
|
||||
throw new InvalidKeySpecException("No public key found.");
|
||||
byte[] encoded = p8.getEncoded();
|
||||
PKCS8Key p8key = null;
|
||||
try {
|
||||
p8key = new ECPrivateKeyImpl(encoded);
|
||||
if (!p8key.hasPublicKey()) {
|
||||
throw new InvalidKeySpecException("No public key found.");
|
||||
}
|
||||
yield new ECPublicKeyImpl(p8key.getPubKeyEncoded());
|
||||
} finally {
|
||||
KeyUtil.clear(encoded, p8key);
|
||||
}
|
||||
yield new ECPublicKeyImpl(p8key.getPubKeyEncoded());
|
||||
}
|
||||
case null -> throw new InvalidKeySpecException(
|
||||
"keySpec must not be null");
|
||||
|
||||
@ -75,6 +75,7 @@ public final class ECPrivateKeyImpl extends PKCS8Key implements ECPrivateKey {
|
||||
@SuppressWarnings("serial") // Type of field is not Serializable
|
||||
private ECParameterSpec params;
|
||||
private byte[] domainParams; //Currently unsupported
|
||||
private final byte SEC1v2 = 1;
|
||||
|
||||
/**
|
||||
* Construct a key from its encoding. Called by the ECKeyFactory.
|
||||
@ -92,34 +93,6 @@ public final class ECPrivateKeyImpl extends PKCS8Key implements ECPrivateKey {
|
||||
throws InvalidKeyException {
|
||||
this.s = s;
|
||||
this.params = params;
|
||||
makeEncoding(s);
|
||||
|
||||
}
|
||||
|
||||
ECPrivateKeyImpl(byte[] s, ECParameterSpec params)
|
||||
throws InvalidKeyException {
|
||||
this.arrayS = s.clone();
|
||||
this.params = params;
|
||||
makeEncoding(s);
|
||||
}
|
||||
|
||||
private void makeEncoding(byte[] s) throws InvalidKeyException {
|
||||
algid = new AlgorithmId
|
||||
(AlgorithmId.EC_oid, ECParameters.getAlgorithmParameters(params));
|
||||
DerOutputStream out = new DerOutputStream();
|
||||
out.putInteger(1); // version 1
|
||||
byte[] privBytes = s.clone();
|
||||
ArrayUtil.reverse(privBytes);
|
||||
out.putOctetString(privBytes);
|
||||
Arrays.fill(privBytes, (byte) 0);
|
||||
DerValue val = DerValue.wrap(DerValue.tag_Sequence, out);
|
||||
privKeyMaterial = val.toByteArray();
|
||||
val.clear();
|
||||
}
|
||||
|
||||
private void makeEncoding(BigInteger s) throws InvalidKeyException {
|
||||
algid = new AlgorithmId(AlgorithmId.EC_oid,
|
||||
ECParameters.getAlgorithmParameters(params));
|
||||
byte[] sArr = s.toByteArray();
|
||||
// convert to fixed-length array
|
||||
int numOctets = (params.getOrder().bitLength() + 7) / 8;
|
||||
@ -129,11 +102,33 @@ public final class ECPrivateKeyImpl extends PKCS8Key implements ECPrivateKey {
|
||||
int length = Math.min(sArr.length, sOctets.length);
|
||||
System.arraycopy(sArr, inPos, sOctets, outPos, length);
|
||||
Arrays.fill(sArr, (byte) 0);
|
||||
try {
|
||||
makeEncoding(sOctets);
|
||||
} finally {
|
||||
Arrays.fill(sOctets, (byte) 0);
|
||||
}
|
||||
}
|
||||
|
||||
ECPrivateKeyImpl(byte[] s, ECParameterSpec params)
|
||||
throws InvalidKeyException {
|
||||
this.arrayS = s.clone();
|
||||
this.params = params;
|
||||
byte[] privBytes = arrayS.clone();
|
||||
ArrayUtil.reverse(privBytes);
|
||||
try {
|
||||
makeEncoding(privBytes);
|
||||
} finally {
|
||||
Arrays.fill(privBytes, (byte) 0);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void makeEncoding(byte[] privBytes) throws InvalidKeyException {
|
||||
algid = new AlgorithmId(AlgorithmId.EC_oid,
|
||||
ECParameters.getAlgorithmParameters(params));
|
||||
DerOutputStream out = new DerOutputStream();
|
||||
out.putInteger(1); // version 1
|
||||
out.putOctetString(sOctets);
|
||||
Arrays.fill(sOctets, (byte) 0);
|
||||
out.putOctetString(privBytes);
|
||||
DerValue val = DerValue.wrap(DerValue.tag_Sequence, out);
|
||||
privKeyMaterial = val.toByteArray();
|
||||
val.clear();
|
||||
@ -181,7 +176,7 @@ public final class ECPrivateKeyImpl extends PKCS8Key implements ECPrivateKey {
|
||||
}
|
||||
DerInputStream data = derValue.data;
|
||||
int version = data.getInteger();
|
||||
if (version != V2) {
|
||||
if (version != SEC1v2) {
|
||||
throw new IOException("Version must be 1");
|
||||
}
|
||||
byte[] privData = data.getOctetString();
|
||||
@ -253,4 +248,40 @@ public final class ECPrivateKeyImpl extends PKCS8Key implements ECPrivateKey {
|
||||
throw new InvalidObjectException(
|
||||
"ECPrivateKeyImpl keys are not directly deserializable");
|
||||
}
|
||||
|
||||
// Parse the SEC1v2 encoding to extract public key, if available.
|
||||
public static BitArray parsePublicBits(byte[] privateBytes) {
|
||||
DerValue seq = null;
|
||||
try {
|
||||
seq = new DerValue(privateBytes);
|
||||
if (seq.tag == DerValue.tag_Sequence) {
|
||||
int version = seq.data.getInteger();
|
||||
if (version == 1) { // EC
|
||||
seq.data.getDerValue(); // read pass the private key
|
||||
if (seq.data.available() != 0) {
|
||||
DerValue derValue = seq.data.getDerValue();
|
||||
// check for optional [0] EC domain parameters
|
||||
if (derValue.isContextSpecific((byte) 0)) {
|
||||
if (seq.data.available() == 0) {
|
||||
return null;
|
||||
}
|
||||
derValue = seq.data.getDerValue();
|
||||
}
|
||||
// [1] public key
|
||||
if (derValue.isContextSpecific((byte) 1)) {
|
||||
derValue = derValue.data.getDerValue();
|
||||
return derValue.getUnalignedBitString();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
} finally {
|
||||
if (seq != null) {
|
||||
seq.clear();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@ -26,6 +26,7 @@
|
||||
package sun.security.ec;
|
||||
|
||||
import sun.security.pkcs.PKCS8Key;
|
||||
import sun.security.util.KeyUtil;
|
||||
|
||||
import java.security.*;
|
||||
import java.security.interfaces.XECKey;
|
||||
@ -159,15 +160,20 @@ public class XDHKeyFactory extends KeyFactorySpi {
|
||||
yield new XDHPublicKeyImpl(params, publicKeySpec.getU());
|
||||
}
|
||||
case PKCS8EncodedKeySpec p8 -> {
|
||||
PKCS8Key p8key = new XDHPrivateKeyImpl(p8.getEncoded());
|
||||
if (!p8key.hasPublicKey()) {
|
||||
throw new InvalidKeySpecException("No public key found.");
|
||||
byte[] encoded = p8.getEncoded();
|
||||
PKCS8Key p8key = new XDHPrivateKeyImpl(encoded);
|
||||
try {
|
||||
if (!p8key.hasPublicKey()) {
|
||||
throw new InvalidKeySpecException("No public key found.");
|
||||
}
|
||||
XDHPublicKeyImpl result =
|
||||
new XDHPublicKeyImpl(p8key.getPubKeyEncoded());
|
||||
checkLockedParams(InvalidKeySpecException::new,
|
||||
result.getParams());
|
||||
yield result;
|
||||
} finally {
|
||||
KeyUtil.clear(encoded, p8key);
|
||||
}
|
||||
XDHPublicKeyImpl result =
|
||||
new XDHPublicKeyImpl(p8key.getPubKeyEncoded());
|
||||
checkLockedParams(InvalidKeySpecException::new,
|
||||
result.getParams());
|
||||
yield result;
|
||||
}
|
||||
case null -> throw new InvalidKeySpecException(
|
||||
"keySpec must not be null");
|
||||
|
||||
@ -26,6 +26,7 @@
|
||||
package sun.security.ec.ed;
|
||||
|
||||
import sun.security.pkcs.PKCS8Key;
|
||||
import sun.security.util.KeyUtil;
|
||||
|
||||
import java.security.*;
|
||||
import java.security.interfaces.*;
|
||||
@ -152,11 +153,17 @@ public class EdDSAKeyFactory extends KeyFactorySpi {
|
||||
yield new EdDSAPublicKeyImpl(params, publicKeySpec.getPoint());
|
||||
}
|
||||
case PKCS8EncodedKeySpec p8 -> {
|
||||
PKCS8Key p8key = new EdDSAPrivateKeyImpl(p8.getEncoded());
|
||||
if (!p8key.hasPublicKey()) {
|
||||
throw new InvalidKeySpecException("No public key found.");
|
||||
byte[] encoded = p8.getEncoded();
|
||||
PKCS8Key p8key = null;
|
||||
try {
|
||||
p8key = new EdDSAPrivateKeyImpl(encoded);
|
||||
if (!p8key.hasPublicKey()) {
|
||||
throw new InvalidKeySpecException("No public key found.");
|
||||
}
|
||||
yield new EdDSAPublicKeyImpl(p8key.getPubKeyEncoded());
|
||||
} finally {
|
||||
KeyUtil.clear(encoded, p8key);
|
||||
}
|
||||
yield new EdDSAPublicKeyImpl(p8key.getPubKeyEncoded());
|
||||
}
|
||||
case null -> throw new InvalidKeySpecException(
|
||||
"keySpec must not be null");
|
||||
|
||||
@ -26,6 +26,7 @@
|
||||
package sun.security.pkcs;
|
||||
|
||||
import jdk.internal.access.SharedSecrets;
|
||||
import sun.security.ec.ECPrivateKeyImpl;
|
||||
import sun.security.util.*;
|
||||
import sun.security.x509.AlgorithmId;
|
||||
import sun.security.x509.X509Key;
|
||||
@ -104,11 +105,29 @@ public class PKCS8Key implements PrivateKey, InternalPrivateKey {
|
||||
}
|
||||
}
|
||||
|
||||
private PKCS8Key(byte[] privEncoding, byte[] pubEncoding)
|
||||
/**
|
||||
* Constructor that takes both public and private encodings.
|
||||
*
|
||||
* If the private key includes a public key encoding (like an EC key in
|
||||
* SEC1v2 format), and a specified public key matches it, the existing
|
||||
* encoding is reused rather than recreated.
|
||||
*/
|
||||
public PKCS8Key(byte[] publicEncoding, byte[] privateEncoding)
|
||||
throws InvalidKeyException {
|
||||
this(privEncoding);
|
||||
pubKeyEncoded = pubEncoding;
|
||||
version = V2;
|
||||
this(privateEncoding);
|
||||
if (publicEncoding != null) {
|
||||
if (pubKeyEncoded != null) {
|
||||
if (!Arrays.equals(pubKeyEncoded, publicEncoding)) {
|
||||
Arrays.fill(privKeyMaterial, (byte) 0x0);
|
||||
throw new InvalidKeyException("PrivateKey " +
|
||||
"encoding has a public key that does not match " +
|
||||
"the given PublicKey");
|
||||
}
|
||||
} else {
|
||||
pubKeyEncoded = publicEncoding;
|
||||
version = V2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int getVersion() {
|
||||
@ -137,6 +156,14 @@ public class PKCS8Key implements PrivateKey, InternalPrivateKey {
|
||||
// Store key material for subclasses to parse
|
||||
privKeyMaterial = val.data.getOctetString();
|
||||
|
||||
// Special check and parsing for ECDSA's SEC1v2 format
|
||||
if (algid.getOID().equals(AlgorithmId.EC_oid)) {
|
||||
var bits = ECPrivateKeyImpl.parsePublicBits(privKeyMaterial);
|
||||
if (bits != null) {
|
||||
pubKeyEncoded = new X509Key(algid, bits).getEncoded();
|
||||
}
|
||||
}
|
||||
|
||||
// PKCS8 v1 typically ends here
|
||||
if (val.data.available() == 0) {
|
||||
return;
|
||||
@ -271,19 +298,24 @@ public class PKCS8Key implements PrivateKey, InternalPrivateKey {
|
||||
* With a given encoded Public and Private key, generate and return a
|
||||
* PKCS8v2 DER-encoded byte[].
|
||||
*
|
||||
* @param pubKeyEncoded DER-encoded PublicKey
|
||||
* @param pubKeyEncoded DER-encoded PublicKey, this may be null.
|
||||
* @param privKeyEncoded DER-encoded PrivateKey
|
||||
* @return DER-encoded byte array
|
||||
* @throws IOException thrown on encoding failure
|
||||
*/
|
||||
public static byte[] getEncoded(byte[] pubKeyEncoded, byte[] privKeyEncoded)
|
||||
throws IOException {
|
||||
PKCS8Key pkcs8Key;
|
||||
try {
|
||||
return new PKCS8Key(privKeyEncoded, pubKeyEncoded).
|
||||
generateEncoding();
|
||||
pkcs8Key = new PKCS8Key(pubKeyEncoded, privKeyEncoded);
|
||||
} catch (InvalidKeyException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
try {
|
||||
return pkcs8Key.generateEncoding().clone();
|
||||
} finally {
|
||||
pkcs8Key.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -295,7 +327,7 @@ public class PKCS8Key implements PrivateKey, InternalPrivateKey {
|
||||
private synchronized byte[] getEncodedInternal() {
|
||||
if (encodedKey == null) {
|
||||
try {
|
||||
encodedKey = generateEncoding();
|
||||
generateEncoding();
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
@ -326,7 +358,6 @@ public class PKCS8Key implements PrivateKey, InternalPrivateKey {
|
||||
throw new IOException(e);
|
||||
}
|
||||
|
||||
// X509Key x = X509Key.parse(pubKeyEncoded);
|
||||
DerOutputStream pubOut = new DerOutputStream();
|
||||
pubOut.putUnalignedBitString(x.getKey());
|
||||
out.writeImplicit(
|
||||
|
||||
@ -27,7 +27,7 @@ package sun.security.provider;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
import java.security.PEMRecord;
|
||||
import java.security.PEM;
|
||||
import java.security.cert.*;
|
||||
import java.util.*;
|
||||
|
||||
@ -559,7 +559,7 @@ public class X509Factory extends CertificateFactorySpi {
|
||||
return bout.toByteArray();
|
||||
} else {
|
||||
try {
|
||||
PEMRecord rec;
|
||||
PEM rec;
|
||||
try {
|
||||
rec = Pem.readPEM(is, (c == '-' ? true : false));
|
||||
} catch (EOFException e) {
|
||||
|
||||
@ -36,12 +36,14 @@ import javax.crypto.interfaces.DHKey;
|
||||
import javax.crypto.interfaces.DHPublicKey;
|
||||
import javax.crypto.spec.DHParameterSpec;
|
||||
import javax.crypto.spec.DHPublicKeySpec;
|
||||
import javax.crypto.spec.PBEKeySpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import javax.security.auth.DestroyFailedException;
|
||||
import jdk.internal.access.SharedSecrets;
|
||||
|
||||
import com.sun.crypto.provider.PBKDF2KeyImpl;
|
||||
import sun.security.jca.JCAUtil;
|
||||
import sun.security.pkcs.PKCS8Key;
|
||||
import sun.security.x509.AlgorithmId;
|
||||
|
||||
/**
|
||||
@ -548,6 +550,22 @@ public final class KeyUtil {
|
||||
throw new IOException("No algorithm detected");
|
||||
}
|
||||
|
||||
|
||||
// Generic method for zeroing arrays and objects
|
||||
public static void clear(Object... list) {
|
||||
for (Object o: list) {
|
||||
switch (o) {
|
||||
case byte[] b -> Arrays.fill(b, (byte)0);
|
||||
case char[] c -> Arrays.fill(c, (char)0);
|
||||
case PKCS8Key p8 -> p8.clear();
|
||||
case PKCS8EncodedKeySpec p8 ->
|
||||
SharedSecrets.getJavaSecuritySpecAccess().clearEncodedKeySpec(p8);
|
||||
case PBEKeySpec pbe -> pbe.clearPassword();
|
||||
case null -> {}
|
||||
default ->
|
||||
throw new IllegalArgumentException(
|
||||
o.getClass().getName() + " not defined in KeyUtil.clear()");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -25,13 +25,18 @@
|
||||
|
||||
package sun.security.util;
|
||||
|
||||
import sun.security.pkcs.PKCS8Key;
|
||||
import sun.security.x509.AlgorithmId;
|
||||
|
||||
import javax.crypto.EncryptedPrivateKeyInfo;
|
||||
import javax.crypto.SecretKeyFactory;
|
||||
import javax.crypto.spec.PBEKeySpec;
|
||||
import java.io.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.PEMRecord;
|
||||
import java.security.Security;
|
||||
import java.security.*;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.security.spec.PKCS8EncodedKeySpec;
|
||||
import java.security.spec.X509EncodedKeySpec;
|
||||
import java.util.Arrays;
|
||||
import java.util.Base64;
|
||||
import java.util.HexFormat;
|
||||
@ -145,6 +150,9 @@ public class Pem {
|
||||
* Read the PEM text and return it in it's three components: header,
|
||||
* base64, and footer.
|
||||
*
|
||||
* The header begins processing when "-----B" is read. At that point
|
||||
* exceptions will be thrown for syntax errors.
|
||||
*
|
||||
* The method will leave the stream after reading the end of line of the
|
||||
* footer or end of file
|
||||
* @param is an InputStream
|
||||
@ -159,15 +167,16 @@ public class Pem {
|
||||
* but the read position in the stream is at the end of the block, so
|
||||
* future reads can be successful.
|
||||
*/
|
||||
public static PEMRecord readPEM(InputStream is, boolean shortHeader)
|
||||
public static PEM readPEM(InputStream is, boolean shortHeader)
|
||||
throws IOException {
|
||||
Objects.requireNonNull(is);
|
||||
|
||||
int hyphen = (shortHeader ? 1 : 0);
|
||||
int eol = 0;
|
||||
|
||||
ByteArrayOutputStream os = new ByteArrayOutputStream(6);
|
||||
// Find starting hyphens
|
||||
|
||||
// Find 5 hyphens followed by a 'B' to start processing the header.
|
||||
boolean headerStarted = false;
|
||||
do {
|
||||
int d = is.read();
|
||||
switch (d) {
|
||||
@ -178,13 +187,20 @@ public class Pem {
|
||||
}
|
||||
throw new EOFException("No PEM data found");
|
||||
}
|
||||
case 'B' -> {
|
||||
if (hyphen == 5) {
|
||||
headerStarted = true;
|
||||
} else {
|
||||
hyphen = 0;
|
||||
}
|
||||
}
|
||||
default -> hyphen = 0;
|
||||
}
|
||||
os.write(d);
|
||||
} while (hyphen != 5);
|
||||
} while (!headerStarted);
|
||||
|
||||
StringBuilder sb = new StringBuilder(64);
|
||||
sb.append("-----");
|
||||
sb.append("-----B");
|
||||
hyphen = 0;
|
||||
int c;
|
||||
|
||||
@ -307,14 +323,14 @@ public class Pem {
|
||||
// If there was data before finding the 5 dashes of the PEM header,
|
||||
// backup 5 characters and save that data.
|
||||
byte[] preData = null;
|
||||
if (os.size() > 5) {
|
||||
preData = Arrays.copyOf(os.toByteArray(), os.size() - 5);
|
||||
if (os.size() > 6) {
|
||||
preData = Arrays.copyOf(os.toByteArray(), os.size() - 6);
|
||||
}
|
||||
|
||||
return new PEMRecord(typeConverter(headerType), data, preData);
|
||||
return new PEM(typeConverter(headerType), data, preData);
|
||||
}
|
||||
|
||||
public static PEMRecord readPEM(InputStream is) throws IOException {
|
||||
public static PEM readPEM(InputStream is) throws IOException {
|
||||
return readPEM(is, false);
|
||||
}
|
||||
|
||||
@ -342,8 +358,115 @@ public class Pem {
|
||||
* is not used with this method.
|
||||
* @return PEM in a string
|
||||
*/
|
||||
public static String pemEncoded(PEMRecord pem) {
|
||||
public static String pemEncoded(PEM pem) {
|
||||
String p = pem.content().replaceAll("(.{64})", "$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 {
|
||||
|
||||
PKCS8EncodedKeySpec p8KeySpec = null;
|
||||
try {
|
||||
SecretKeyFactory skf = SecretKeyFactory.getInstance(ekpi.getAlgName());
|
||||
p8KeySpec = ekpi.getKeySpec(skf.generateSecret(keySpec));
|
||||
return p8KeySpec.getEncoded();
|
||||
} catch (InvalidKeySpecException e) {
|
||||
throw new InvalidKeyException(e);
|
||||
} finally {
|
||||
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,
|
||||
Provider provider) throws InvalidKeyException {
|
||||
|
||||
PrivateKey privKey;
|
||||
PublicKey pubKey = null;
|
||||
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);
|
||||
}
|
||||
|
||||
try {
|
||||
if (provider == null) {
|
||||
kf = KeyFactory.getInstance(p8key.getAlgorithm());
|
||||
} else {
|
||||
kf = KeyFactory.getInstance(p8key.getAlgorithm(), provider);
|
||||
}
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
KeyUtil.clear(p8KeySpec, p8key);
|
||||
throw new InvalidKeyException("Unable to find the algorithm: " +
|
||||
p8key.getAlgorithm(), e);
|
||||
}
|
||||
|
||||
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(
|
||||
new X509EncodedKeySpec(p8key.getPubKeyEncoded()));
|
||||
} else {
|
||||
// In case decode() could not read the public key, the
|
||||
// KeyFactory can try. Failure is ok as there may not
|
||||
// be a public key in the encoding.
|
||||
try {
|
||||
pubKey = kf.generatePublic(p8KeySpec);
|
||||
} catch (InvalidKeySpecException e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
} catch (InvalidKeySpecException e) {
|
||||
throw new InvalidKeyException(e);
|
||||
} finally {
|
||||
KeyUtil.clear(p8KeySpec, p8key);
|
||||
}
|
||||
if (pair && pubKey != null) {
|
||||
return new KeyPair(pubKey, privKey);
|
||||
}
|
||||
return privKey;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -26,7 +26,7 @@
|
||||
import javax.crypto.EncryptedPrivateKeyInfo;
|
||||
import java.security.DEREncodable;
|
||||
import java.security.KeyPair;
|
||||
import java.security.PEMRecord;
|
||||
import java.security.PEM;
|
||||
import java.security.cert.X509CRL;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.security.interfaces.*;
|
||||
@ -48,6 +48,17 @@ class PEMData {
|
||||
-----END PRIVATE KEY-----
|
||||
""", KeyPair.class, "SunEC");
|
||||
|
||||
// EC 256 with a domain parameter & public key
|
||||
public static final Entry ecsecp256dom0 = new Entry("ecsecp256dom0",
|
||||
"""
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQgkW3Jx561NlEgBnut
|
||||
KwDdi3cNwu7YYD/QtJ+9+AEBdoqgCgYIKoZIzj0DAQehRANCAASL+REY4vvAI9M3
|
||||
gonaml5K3lRgHq5w+OO4oO0VNduC44gUN1nrk7/wdNSpL+xXNEX52Dsff+2RD/fo
|
||||
p224ANvB
|
||||
-----END PRIVATE KEY-----
|
||||
""", KeyPair.class, "SunEC");
|
||||
|
||||
public static final Entry rsapriv = new Entry("rsapriv",
|
||||
"""
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
@ -149,7 +160,7 @@ class PEMData {
|
||||
-----END PRIVATE KEY-----
|
||||
""", RSAPrivateKey.class, "SunRsaSign");
|
||||
|
||||
public static final Entry ec25519priv = new Entry("ed25519priv",
|
||||
public static final Entry ed25519priv = new Entry("ed25519priv",
|
||||
"""
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MC4CAQAwBQYDK2VwBCIEIFFZsmD+OKk67Cigc84/2fWtlKsvXWLSoMJ0MHh4jI4I
|
||||
@ -189,6 +200,7 @@ class PEMData {
|
||||
-----END PUBLIC KEY-----
|
||||
""", RSAPublicKey.class, "SunRsaSign");
|
||||
|
||||
// This is the public key contained in ecsecp256
|
||||
public static final Entry ecsecp256pub = new Entry("ecsecp256pub", """
|
||||
-----BEGIN PUBLIC KEY-----
|
||||
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEi/kRGOL7wCPTN4KJ2ppeSt5UYB6u
|
||||
@ -286,6 +298,19 @@ class PEMData {
|
||||
-----END RSA PRIVATE KEY-----
|
||||
""", RSAPrivateKey.class, "SunRsaSign");
|
||||
|
||||
static final Entry ecsecp256ekpi = new Entry("ecsecp256ekpi",
|
||||
"""
|
||||
-----BEGIN ENCRYPTED PRIVATE KEY-----
|
||||
MIH0MF8GCSqGSIb3DQEFDTBSMDEGCSqGSIb3DQEFDDAkBBDhqUj1Oadj1GZXUMXT
|
||||
b3QEAgIIADAMBggqhkiG9w0CCQUAMB0GCWCGSAFlAwQBAgQQitxCfcZcMtoNu+X+
|
||||
PQk+/wSBkFL1NddKkUL2tRv6pNf1TR7eI7qJReGRgJexU/6pDN+UQS5e5qSySa7E
|
||||
k1m2pUHgZlySUblXZj9nOzCsNFfq/jxlL15ZpAviAM2fRINnNEJcvoB+qZTS5cRb
|
||||
Xs3wC7wymHW3EdIZ9sxfSHq9t7j9SnC1jGHjno0v1rKcdIvJtYloxsRYjsG/Sxhz
|
||||
uNYnx8AMuQ==
|
||||
-----END ENCRYPTED PRIVATE KEY-----
|
||||
""", EncryptedPrivateKeyInfo.class, "SunEC", "fish".toCharArray());
|
||||
|
||||
|
||||
static final Entry ed25519ep8 = new Entry("ed25519ep8",
|
||||
"""
|
||||
-----BEGIN ENCRYPTED PRIVATE KEY-----
|
||||
@ -450,7 +475,7 @@ class PEMData {
|
||||
MQYMBGZpc2gwCgYIKoZIzj0EAwIDRwAwRAIgUBTdrMDE4BqruYRh1rRyKQBf48WR
|
||||
kIX8R4dBK9h1VRcCIEBR2Mzvku/huTbWTwKVlXBZeEmwIlxKwpRepPtViXcW
|
||||
-----END CERTIFICATE REQUEST-----
|
||||
""", PEMRecord.class, "SunEC");
|
||||
""", PEM.class, "SunEC");
|
||||
|
||||
public static final String preData = "TEXT BLAH TEXT BLAH" +
|
||||
System.lineSeparator();
|
||||
@ -471,7 +496,7 @@ class PEMData {
|
||||
MQYMBGZpc2gwCgYIKoZIzj0EAwIDRwAwRAIgUBTdrMDE4BqruYRh1rRyKQBf48WR
|
||||
kIX8R4dBK9h1VRcCIEBR2Mzvku/huTbWTwKVlXBZeEmwIlxKwpRepPtViXcW
|
||||
-----END CERTIFICATE REQUEST-----
|
||||
""" + postData, PEMRecord.class, "SunEC");
|
||||
""" + postData, PEM.class, "SunEC");
|
||||
|
||||
final static Pattern CR = Pattern.compile("\r");
|
||||
final static Pattern LF = Pattern.compile("\n");
|
||||
@ -564,8 +589,9 @@ class PEMData {
|
||||
privList.add(rsapsspriv);
|
||||
privList.add(rsaprivbc);
|
||||
privList.add(ecsecp256);
|
||||
privList.add(ecsecp256dom0);
|
||||
privList.add(ecsecp384);
|
||||
privList.add(ec25519priv);
|
||||
privList.add(ed25519priv);
|
||||
privList.add(ed25519ekpi); // The non-EKPI version needs decryption
|
||||
privList.add(rsaOpenSSL);
|
||||
oasList.add(oasrfc8410);
|
||||
|
||||
@ -53,8 +53,11 @@ import sun.security.util.Pem;
|
||||
public class PEMDecoderTest {
|
||||
|
||||
static HexFormat hex = HexFormat.of();
|
||||
static final PEMDecoder d = PEMDecoder.of();
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
PEMDecoder decr;
|
||||
|
||||
System.out.println("Decoder test:");
|
||||
PEMData.entryList.forEach(entry -> test(entry, false));
|
||||
System.out.println("Decoder test withFactory:");
|
||||
@ -89,12 +92,12 @@ public class PEMDecoderTest {
|
||||
System.out.println("Decoder test ecsecp256:");
|
||||
testFailure(PEMData.ecsecp256pub.makeNoCRLF("pubecpem-no"));
|
||||
System.out.println("Decoder test RSAcert with decryption Decoder:");
|
||||
PEMDecoder d = PEMDecoder.of().withDecryption("123".toCharArray());
|
||||
d.decode(PEMData.rsaCert.pem());
|
||||
decr = d.withDecryption("123".toCharArray());
|
||||
decr.decode(PEMData.rsaCert.pem());
|
||||
System.out.println("Decoder test ecsecp256 private key with decryption Decoder:");
|
||||
((KeyPair) d.decode(PEMData.ecsecp256.pem())).getPrivate();
|
||||
((KeyPair) decr.decode(PEMData.ecsecp256.pem())).getPrivate();
|
||||
System.out.println("Decoder test ecsecp256 to P8EKS:");
|
||||
d.decode(PEMData.ecsecp256.pem(), PKCS8EncodedKeySpec.class);
|
||||
decr.decode(PEMData.ecsecp256.pem(), PKCS8EncodedKeySpec.class);
|
||||
|
||||
System.out.println("Checking if decode() returns the same encoding:");
|
||||
PEMData.privList.forEach(PEMDecoderTest::testDERCheck);
|
||||
@ -111,11 +114,11 @@ public class PEMDecoderTest {
|
||||
System.out.println("Checking if ecCSR:");
|
||||
test(PEMData.ecCSR);
|
||||
System.out.println("Checking if ecCSR with preData:");
|
||||
DEREncodable result = PEMDecoder.of().decode(PEMData.ecCSRWithData.pem(), PEMRecord.class);
|
||||
if (result instanceof PEMRecord rec) {
|
||||
DEREncodable 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()));
|
||||
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) {
|
||||
@ -128,35 +131,34 @@ public class PEMDecoderTest {
|
||||
}
|
||||
|
||||
System.out.println("Decoding RSA pub using class PEMRecord:");
|
||||
result = PEMDecoder.of().decode(PEMData.rsapub.pem(), PEMRecord.class);
|
||||
if (!(result instanceof PEMRecord)) {
|
||||
result = d.decode(PEMData.rsapub.pem(), PEM.class);
|
||||
if (!(result instanceof PEM)) {
|
||||
throw new AssertionError("pubecpem didn't return a PEMRecord");
|
||||
}
|
||||
if (((PEMRecord) result).type().compareTo(Pem.PUBLIC_KEY) != 0) {
|
||||
if (((PEM) result).type().compareTo(Pem.PUBLIC_KEY) != 0) {
|
||||
throw new AssertionError("pubecpem PEMRecord didn't decode as a Public Key");
|
||||
}
|
||||
|
||||
testInputStream();
|
||||
testPEMRecord(PEMData.rsapub);
|
||||
testPEMRecord(PEMData.ecCert);
|
||||
testPEMRecord(PEMData.ec25519priv);
|
||||
testPEMRecord(PEMData.ed25519priv);
|
||||
testPEMRecord(PEMData.ecCSR);
|
||||
testPEMRecord(PEMData.ecCSRWithData);
|
||||
testPEMRecordDecode(PEMData.rsapub);
|
||||
testPEMRecordDecode(PEMData.ecCert);
|
||||
testPEMRecordDecode(PEMData.ec25519priv);
|
||||
testPEMRecordDecode(PEMData.ed25519priv);
|
||||
testPEMRecordDecode(PEMData.ecCSR);
|
||||
testPEMRecordDecode(PEMData.ecCSRWithData);
|
||||
|
||||
d = PEMDecoder.of();
|
||||
System.out.println("Check leadingData is null with back-to-back PEMs: ");
|
||||
String s = new PEMRecord("ONE", "1212").toString()
|
||||
+ new PEMRecord("TWO", "3434").toString();
|
||||
var ins = new ByteArrayInputStream(s.getBytes(StandardCharsets.UTF_8));
|
||||
if (d.decode(ins, PEMRecord.class).leadingData() != null) {
|
||||
String s = new PEM("ONE", "1212").toString()
|
||||
+ new PEM("TWO", "3434").toString();
|
||||
ByteArrayInputStream bis = new ByteArrayInputStream(s.getBytes(StandardCharsets.UTF_8));
|
||||
if (d.decode(bis, PEM.class).leadingData() != null) {
|
||||
throw new AssertionError("leading data not null on first pem");
|
||||
}
|
||||
if (d.decode(ins, PEMRecord.class).leadingData() != null) {
|
||||
if (d.decode(bis, PEM.class).leadingData() != null) {
|
||||
throw new AssertionError("leading data not null on second pem");
|
||||
}
|
||||
System.out.println("PASS");
|
||||
@ -173,8 +175,8 @@ public class PEMDecoderTest {
|
||||
}
|
||||
|
||||
// PBE
|
||||
System.out.println("EncryptedPrivateKeyInfo.encryptKey with PBE: ");
|
||||
ekpi = EncryptedPrivateKeyInfo.encryptKey(privateKey,
|
||||
System.out.println("EncryptedPrivateKeyInfo.encrypt with PBE: ");
|
||||
ekpi = EncryptedPrivateKeyInfo.encrypt(privateKey,
|
||||
"password".toCharArray(),"PBEWithMD5AndDES", null, null);
|
||||
try {
|
||||
ekpi.getKey("password".toCharArray());
|
||||
@ -184,8 +186,8 @@ public class PEMDecoderTest {
|
||||
}
|
||||
|
||||
// PBES2
|
||||
System.out.println("EncryptedPrivateKeyInfo.encryptKey with default: ");
|
||||
ekpi = EncryptedPrivateKeyInfo.encryptKey(privateKey
|
||||
System.out.println("EncryptedPrivateKeyInfo.encrypt with default: ");
|
||||
ekpi = EncryptedPrivateKeyInfo.encrypt(privateKey
|
||||
, "password".toCharArray());
|
||||
try {
|
||||
ekpi.getKey("password".toCharArray());
|
||||
@ -197,6 +199,13 @@ public class PEMDecoderTest {
|
||||
|
||||
System.out.println("Decoder test testCoefZero:");
|
||||
testCoefZero(PEMData.rsaCrtCoefZeroPriv);
|
||||
|
||||
// leadingData can contain dashes
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
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);
|
||||
}
|
||||
|
||||
static void testInputStream() throws IOException {
|
||||
@ -211,10 +220,10 @@ public class PEMDecoderTest {
|
||||
ByteArrayInputStream is = new ByteArrayInputStream(ba.toByteArray());
|
||||
|
||||
System.out.println("Decoding 2 RSA pub with pre & post data:");
|
||||
PEMRecord obj;
|
||||
PEM obj;
|
||||
int keys = 0;
|
||||
while (keys++ < 2) {
|
||||
obj = PEMDecoder.of().decode(is, PEMRecord.class);
|
||||
obj = d.decode(is, PEM.class);
|
||||
if (!PEMData.preData.equalsIgnoreCase(
|
||||
new String(obj.leadingData()))) {
|
||||
System.out.println("expected: \"" + PEMData.preData + "\"");
|
||||
@ -225,7 +234,7 @@ public class PEMDecoderTest {
|
||||
System.out.println(" Read public key.");
|
||||
}
|
||||
try {
|
||||
PEMDecoder.of().decode(is, PEMRecord.class);
|
||||
d.decode(is, PEM.class);
|
||||
throw new AssertionError("3rd entry returned a PEMRecord");
|
||||
} catch (EOFException e) {
|
||||
System.out.println("Success: No 3rd entry found. EOFE thrown.");
|
||||
@ -234,7 +243,7 @@ public class PEMDecoderTest {
|
||||
// End of stream
|
||||
try {
|
||||
System.out.println("Failed: There should be no PEMRecord: " +
|
||||
PEMDecoder.of().decode(is, PEMRecord.class));
|
||||
d.decode(is, PEM.class));
|
||||
} catch (EOFException e) {
|
||||
System.out.println("Success");
|
||||
return;
|
||||
@ -250,22 +259,22 @@ public class PEMDecoderTest {
|
||||
static void testCertTypeConverter(PEMData.Entry entry) throws CertificateEncodingException {
|
||||
String certPem = entry.pem().replace("CERTIFICATE", "X509 CERTIFICATE");
|
||||
Asserts.assertEqualsByteArray(entry.der(),
|
||||
PEMDecoder.of().decode(certPem, X509Certificate.class).getEncoded());
|
||||
d.decode(certPem, X509Certificate.class).getEncoded());
|
||||
|
||||
certPem = entry.pem().replace("CERTIFICATE", "X.509 CERTIFICATE");
|
||||
Asserts.assertEqualsByteArray(entry.der(),
|
||||
PEMDecoder.of().decode(certPem, X509Certificate.class).getEncoded());
|
||||
d.decode(certPem, X509Certificate.class).getEncoded());
|
||||
}
|
||||
|
||||
// test that when the crtCoeff is zero, the key is decoded but only the modulus and private
|
||||
// exponent are used resulting in a different der
|
||||
static void testCoefZero(PEMData.Entry entry) {
|
||||
RSAPrivateKey decoded = PEMDecoder.of().decode(entry.pem(), RSAPrivateKey.class);
|
||||
RSAPrivateKey decoded = d.decode(entry.pem(), RSAPrivateKey.class);
|
||||
Asserts.assertNotEqualsByteArray(decoded.getEncoded(), entry.der());
|
||||
}
|
||||
|
||||
static void testPEMRecord(PEMData.Entry entry) {
|
||||
PEMRecord r = PEMDecoder.of().decode(entry.pem(), PEMRecord.class);
|
||||
PEM r = d.decode(entry.pem(), PEM.class);
|
||||
String expected = entry.pem().split("-----")[2].replace(System.lineSeparator(), "");
|
||||
try {
|
||||
PEMData.checkResults(expected, r.content());
|
||||
@ -285,7 +294,7 @@ public class PEMDecoderTest {
|
||||
case Pem.X509_CRL ->
|
||||
entry.clazz().isAssignableFrom(X509CRL.class);
|
||||
case "CERTIFICATE REQUEST" ->
|
||||
entry.clazz().isAssignableFrom(PEMRecord.class);
|
||||
entry.clazz().isAssignableFrom(PEM.class);
|
||||
default -> false;
|
||||
};
|
||||
|
||||
@ -300,8 +309,8 @@ public class PEMDecoderTest {
|
||||
|
||||
|
||||
static void testPEMRecordDecode(PEMData.Entry entry) {
|
||||
PEMRecord r = PEMDecoder.of().decode(entry.pem(), PEMRecord.class);
|
||||
DEREncodable de = PEMDecoder.of().decode(r.toString());
|
||||
PEM r = d.decode(entry.pem(), PEM.class);
|
||||
DEREncodable de = d.decode(r.toString());
|
||||
|
||||
boolean result = switch(r.type()) {
|
||||
case Pem.PRIVATE_KEY ->
|
||||
@ -311,7 +320,7 @@ public class PEMDecoderTest {
|
||||
case Pem.CERTIFICATE, Pem.X509_CERTIFICATE ->
|
||||
(de instanceof X509Certificate);
|
||||
case Pem.X509_CRL -> (de instanceof X509CRL);
|
||||
case "CERTIFICATE REQUEST" -> (de instanceof PEMRecord);
|
||||
case "CERTIFICATE REQUEST" -> (de instanceof PEM);
|
||||
default -> false;
|
||||
};
|
||||
|
||||
@ -332,7 +341,7 @@ public class PEMDecoderTest {
|
||||
|
||||
static void testFailure(PEMData.Entry entry, Class c) {
|
||||
try {
|
||||
test(entry.pem(), c, PEMDecoder.of());
|
||||
test(entry.pem(), c, d);
|
||||
if (entry.pem().indexOf('\r') != -1) {
|
||||
System.out.println("Found a CR.");
|
||||
}
|
||||
@ -351,9 +360,11 @@ public class PEMDecoderTest {
|
||||
}
|
||||
|
||||
static DEREncodable testEncrypted(PEMData.Entry entry) {
|
||||
PEMDecoder decoder = PEMDecoder.of();
|
||||
PEMDecoder decoder;
|
||||
if (!Objects.equals(entry.clazz(), EncryptedPrivateKeyInfo.class)) {
|
||||
decoder = decoder.withDecryption(entry.password());
|
||||
decoder = d.withDecryption(entry.password());
|
||||
} else {
|
||||
decoder = d;
|
||||
}
|
||||
|
||||
try {
|
||||
@ -381,9 +392,9 @@ public class PEMDecoderTest {
|
||||
PEMDecoder pemDecoder;
|
||||
if (withFactory) {
|
||||
Provider provider = Security.getProvider(entry.provider());
|
||||
pemDecoder = PEMDecoder.of().withFactory(provider);
|
||||
pemDecoder = d.withFactory(provider);
|
||||
} else {
|
||||
pemDecoder = PEMDecoder.of();
|
||||
pemDecoder = d;
|
||||
}
|
||||
DEREncodable r = test(entry.pem(), entry.clazz(), pemDecoder);
|
||||
System.out.println("PASS (" + entry.name() + ")");
|
||||
@ -446,9 +457,8 @@ public class PEMDecoderTest {
|
||||
// result is the same
|
||||
static void testTwoKeys() throws IOException {
|
||||
PublicKey p1, p2;
|
||||
PEMDecoder pd = PEMDecoder.of();
|
||||
p1 = pd.decode(PEMData.rsapub.pem(), RSAPublicKey.class);
|
||||
p2 = pd.decode(PEMData.rsapub.pem(), RSAPublicKey.class);
|
||||
p1 = d.decode(PEMData.rsapub.pem(), RSAPublicKey.class);
|
||||
p2 = d.decode(PEMData.rsapub.pem(), RSAPublicKey.class);
|
||||
if (!Arrays.equals(p1.getEncoded(), p2.getEncoded())) {
|
||||
System.err.println("These two should have matched:");
|
||||
System.err.println(hex.parseHex(new String(p1.getEncoded())));
|
||||
@ -460,7 +470,7 @@ public class PEMDecoderTest {
|
||||
|
||||
private static void testPKCS8Key(PEMData.Entry entry) {
|
||||
try {
|
||||
PKCS8Key key = PEMDecoder.of().decode(entry.pem(), PKCS8Key.class);
|
||||
PKCS8Key key = d.decode(entry.pem(), PKCS8Key.class);
|
||||
PKCS8EncodedKeySpec spec =
|
||||
new PKCS8EncodedKeySpec(key.getEncoded());
|
||||
|
||||
@ -472,7 +482,7 @@ public class PEMDecoderTest {
|
||||
}
|
||||
|
||||
static void testClass(PEMData.Entry entry, Class clazz) throws IOException {
|
||||
var pk = PEMDecoder.of().decode(entry.pem(), clazz);
|
||||
var pk = d.decode(entry.pem(), clazz);
|
||||
}
|
||||
|
||||
static void testClass(PEMData.Entry entry, Class clazz, boolean pass)
|
||||
@ -506,7 +516,7 @@ public class PEMDecoderTest {
|
||||
return;
|
||||
}
|
||||
|
||||
PKCS8EncodedKeySpec p8 = PEMDecoder.of().decode(entry.pem(),
|
||||
PKCS8EncodedKeySpec p8 = d.decode(entry.pem(),
|
||||
PKCS8EncodedKeySpec.class);
|
||||
int result = Arrays.compare(entry.der(), p8.getEncoded());
|
||||
if (result != 0) {
|
||||
@ -531,8 +541,8 @@ public class PEMDecoderTest {
|
||||
byte[] data = "12345678".getBytes();
|
||||
PrivateKey privateKey;
|
||||
|
||||
DEREncodable d = PEMDecoder.of().decode(entry.pem());
|
||||
switch (d) {
|
||||
DEREncodable der = d.decode(entry.pem());
|
||||
switch (der) {
|
||||
case PrivateKey p -> privateKey = p;
|
||||
case KeyPair kp -> privateKey = kp.getPrivate();
|
||||
case EncryptedPrivateKeyInfo e -> {
|
||||
@ -563,7 +573,7 @@ public class PEMDecoderTest {
|
||||
};
|
||||
|
||||
try {
|
||||
if (d instanceof PrivateKey) {
|
||||
if (der instanceof PrivateKey) {
|
||||
s = Signature.getInstance(algorithm);
|
||||
if (spec != null) {
|
||||
s.setParameter(spec);
|
||||
@ -572,12 +582,12 @@ public class PEMDecoderTest {
|
||||
s.update(data);
|
||||
s.sign();
|
||||
System.out.println("PASS (Sign): " + entry.name());
|
||||
} else if (d instanceof KeyPair) {
|
||||
} else if (der instanceof KeyPair) {
|
||||
s = Signature.getInstance(algorithm);
|
||||
s.initSign(privateKey);
|
||||
s.update(data);
|
||||
byte[] sig = s.sign();
|
||||
s.initVerify(((KeyPair)d).getPublic());
|
||||
s.initVerify(((KeyPair)der).getPublic());
|
||||
s.verify(sig);
|
||||
System.out.println("PASS (Sign/Verify): " + entry.name());
|
||||
} else {
|
||||
|
||||
@ -62,6 +62,10 @@ public class PEMEncoderTest {
|
||||
public static void main(String[] args) throws Exception {
|
||||
pkcs8DefaultAlgExpect = args[0];
|
||||
PEMEncoder encoder = PEMEncoder.of();
|
||||
PEMDecoder decoder = PEMDecoder.of();
|
||||
EncryptedPrivateKeyInfo ekpi;
|
||||
KeyPair kp;
|
||||
PEM pem;
|
||||
|
||||
// These entries are removed
|
||||
var newEntryList = new ArrayList<>(PEMData.entryList);
|
||||
@ -70,14 +74,13 @@ public class PEMEncoderTest {
|
||||
newEntryList.remove(PEMData.getEntry("ecsecp384"));
|
||||
keymap = generateObjKeyMap(newEntryList);
|
||||
System.out.println("Same instance re-encode test:");
|
||||
keymap.keySet().stream().forEach(key -> test(key, encoder));
|
||||
keymap.keySet().forEach(key -> test(key, encoder));
|
||||
System.out.println("New instance re-encode test:");
|
||||
keymap.keySet().stream().forEach(key -> test(key, PEMEncoder.of()));
|
||||
keymap.keySet().forEach(key -> test(key, PEMEncoder.of()));
|
||||
System.out.println("Same instance re-encode testToString:");
|
||||
keymap.keySet().stream().forEach(key -> testToString(key, encoder));
|
||||
keymap.keySet().forEach(key -> testToString(key, encoder));
|
||||
System.out.println("New instance re-encode testToString:");
|
||||
keymap.keySet().stream().forEach(key -> testToString(key,
|
||||
PEMEncoder.of()));
|
||||
keymap.keySet().forEach(key -> testToString(key, PEMEncoder.of()));
|
||||
System.out.println("Same instance Encoder testEncodedKeySpec:");
|
||||
testEncodedKeySpec(encoder);
|
||||
System.out.println("New instance Encoder testEncodedKeySpec:");
|
||||
@ -86,14 +89,14 @@ public class PEMEncoderTest {
|
||||
testEmptyAndNullKey(encoder);
|
||||
keymap = generateObjKeyMap(PEMData.encryptedList);
|
||||
System.out.println("Same instance Encoder match test:");
|
||||
keymap.keySet().stream().forEach(key -> testEncryptedMatch(key, encoder));
|
||||
keymap.keySet().forEach(key -> testEncryptedMatch(key, encoder));
|
||||
System.out.println("Same instance Encoder new withEnc test:");
|
||||
keymap.keySet().stream().forEach(key -> testEncrypted(key, encoder));
|
||||
keymap.keySet().forEach(key -> testEncrypted(key, encoder));
|
||||
System.out.println("New instance Encoder and withEnc test:");
|
||||
keymap.keySet().stream().forEach(key -> testEncrypted(key, PEMEncoder.of()));
|
||||
keymap.keySet().forEach(key -> testEncrypted(key, PEMEncoder.of()));
|
||||
System.out.println("Same instance encrypted Encoder test:");
|
||||
PEMEncoder encEncoder = encoder.withEncryption("fish".toCharArray());
|
||||
keymap.keySet().stream().forEach(key -> testSameEncryptor(key, encEncoder));
|
||||
keymap.keySet().forEach(key -> testSameEncryptor(key, encEncoder));
|
||||
try {
|
||||
encoder.withEncryption(null);
|
||||
} catch (Exception e) {
|
||||
@ -102,17 +105,51 @@ public class PEMEncoderTest {
|
||||
}
|
||||
}
|
||||
|
||||
PEMDecoder d = PEMDecoder.of();
|
||||
PEMRecord pemRecord =
|
||||
d.decode(PEMData.ed25519ep8.pem(), PEMRecord.class);
|
||||
PEMData.checkResults(PEMData.ed25519ep8, pemRecord.toString());
|
||||
pem = decoder.decode(PEMData.ed25519ep8.pem(), PEM.class);
|
||||
PEMData.checkResults(PEMData.ed25519ep8, pem.toString());
|
||||
|
||||
// test PemRecord is encapsulated with PEM header and footer on encoding
|
||||
// test PEM is encapsulated with PEM header and footer on encoding
|
||||
String[] pemLines = PEMData.ed25519ep8.pem().split("\n");
|
||||
String[] pemNoHeaderFooter = Arrays.copyOfRange(pemLines, 1, pemLines.length - 1);
|
||||
PEMRecord pemR = new PEMRecord("ENCRYPTED PRIVATE KEY", String.join("\n",
|
||||
pem = new PEM("ENCRYPTED PRIVATE KEY", String.join("\n",
|
||||
pemNoHeaderFooter));
|
||||
PEMData.checkResults(PEMData.ed25519ep8.pem(), encoder.encodeToString(pemR));
|
||||
PEMData.checkResults(PEMData.ed25519ep8.pem(), encoder.encodeToString(pem));
|
||||
|
||||
// Verify the same private key bytes are returned with an ECDSA private
|
||||
// key PEM and an encrypted PEM.
|
||||
kp = decoder.decode(PEMData.ecsecp256.pem(), KeyPair.class);
|
||||
var origPriv = kp.getPrivate();
|
||||
String s = encoder.withEncryption(PEMData.ecsecp256ekpi.password()).encodeToString(kp);
|
||||
kp = decoder.withDecryption(PEMData.ecsecp256ekpi.password()).decode(s, KeyPair.class);
|
||||
var newPriv = kp.getPrivate();
|
||||
if (!Arrays.equals(origPriv.getEncoded(), newPriv.getEncoded())) {
|
||||
throw new AssertionError("compare fails");
|
||||
}
|
||||
|
||||
// Encoded non-encrypted Keypair
|
||||
kp = KeyPairGenerator.getInstance("XDH").generateKeyPair();
|
||||
s = encoder.encodeToString(kp);
|
||||
decoder.decode(s, KeyPair.class);
|
||||
|
||||
// EmptyKey for the PrivateKey in a KeyPair. Uses keypair from above.
|
||||
try {
|
||||
encoder.encode(new KeyPair(kp.getPublic(), new EmptyKey()));
|
||||
throw new AssertionError("encoder accepted a empty private key encoding");
|
||||
} catch (IllegalArgumentException _) {}
|
||||
|
||||
// NullKey for the PrivateKey in a KeyPair. Uses keypair from above.
|
||||
try {
|
||||
encoder.encode(new KeyPair(kp.getPublic(), new NullKey()));
|
||||
throw new AssertionError("encoder accepted a empty private key encoding");
|
||||
} catch (IllegalArgumentException _) {}
|
||||
|
||||
ekpi = decoder.decode(PEMData.ecsecp256ekpi.pem(),
|
||||
EncryptedPrivateKeyInfo.class);
|
||||
try {
|
||||
encoder.withEncryption("blah".toCharArray()).encode(ekpi);
|
||||
throw new AssertionError("encoder tried to encrypt " +
|
||||
"an EncryptedPrivateKeyInfo.");
|
||||
} catch (IllegalArgumentException _) {}
|
||||
}
|
||||
|
||||
static Map generateObjKeyMap(List<PEMData.Entry> list) {
|
||||
@ -215,7 +252,7 @@ public class PEMEncoderTest {
|
||||
EncryptedPrivateKeyInfo ekpi = PEMDecoder.of().decode(entry.pem(),
|
||||
EncryptedPrivateKeyInfo.class);
|
||||
if (entry.password() != null) {
|
||||
EncryptedPrivateKeyInfo.encryptKey(pkey, entry.password(),
|
||||
EncryptedPrivateKeyInfo.encrypt(pkey, entry.password(),
|
||||
Pem.DEFAULT_ALGO, ekpi.getAlgParameters().
|
||||
getParameterSpec(PBEParameterSpec.class),
|
||||
null);
|
||||
@ -267,4 +304,16 @@ public class PEMEncoderTest {
|
||||
@Override
|
||||
public byte[] getEncoded() { return new byte[0]; }
|
||||
}
|
||||
|
||||
private static class NullKey implements PrivateKey {
|
||||
@Override
|
||||
public String getAlgorithm() { return "Test"; }
|
||||
|
||||
@Override
|
||||
public String getFormat() { return "Test"; }
|
||||
|
||||
@Override
|
||||
public byte[] getEncoded() { return null; }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -48,7 +48,7 @@ import java.util.Arrays;
|
||||
|
||||
import static jdk.test.lib.Asserts.assertEquals;
|
||||
|
||||
public class EncryptKey {
|
||||
public class Encrypt {
|
||||
|
||||
private static final String encEdECKey =
|
||||
"""
|
||||
@ -74,7 +74,7 @@ public class EncryptKey {
|
||||
AlgorithmParameters ap = ekpi.getAlgParameters();
|
||||
|
||||
// Test encryptKey(PrivateKey, char[], String, ... )
|
||||
var e = EncryptedPrivateKeyInfo.encryptKey(priKey, password,
|
||||
var e = EncryptedPrivateKeyInfo.encrypt(priKey, password,
|
||||
ekpi.getAlgName(), ap.getParameterSpec(PBEParameterSpec.class),
|
||||
null);
|
||||
if (!Arrays.equals(ekpi.getEncryptedData(), e.getEncryptedData())) {
|
||||
@ -83,45 +83,53 @@ public class EncryptKey {
|
||||
}
|
||||
|
||||
// Test encryptKey(PrivateKey, char[], String, ...) with provider
|
||||
e = EncryptedPrivateKeyInfo.encryptKey(priKey, password, ekpi.getAlgName(),
|
||||
ap.getParameterSpec(PBEParameterSpec.class), p);
|
||||
e = EncryptedPrivateKeyInfo.encrypt(priKey, password, ekpi.getAlgName(),
|
||||
ap.getParameterSpec(PBEParameterSpec.class), p);
|
||||
if (!Arrays.equals(ekpi.getEncryptedData(), e.getEncryptedData())) {
|
||||
throw new AssertionError("encryptKey() didn't match" +
|
||||
" with expected.");
|
||||
" with expected.");
|
||||
}
|
||||
|
||||
// Test encryptKey(PrivateKey, char[], String, ...) with provider and null algorithm
|
||||
e = EncryptedPrivateKeyInfo.encryptKey(priKey, password, null, null,
|
||||
p);
|
||||
e = EncryptedPrivateKeyInfo.encrypt(priKey, password, Pem.DEFAULT_ALGO, null, p);
|
||||
assertEquals(e.getAlgName(), Pem.DEFAULT_ALGO);
|
||||
|
||||
// Test encryptKey(PrivateKey, Key, String, ...)
|
||||
e = EncryptedPrivateKeyInfo.encryptKey(priKey, key, ekpi.getAlgName(),
|
||||
ap.getParameterSpec(PBEParameterSpec.class),null, null);
|
||||
e = EncryptedPrivateKeyInfo.encrypt(priKey, key, ekpi.getAlgName(),
|
||||
ap.getParameterSpec(PBEParameterSpec.class), null, null);
|
||||
if (!Arrays.equals(ekpi.getEncryptedData(), e.getEncryptedData())) {
|
||||
throw new AssertionError("encryptKey() didn't match" +
|
||||
" with expected.");
|
||||
}
|
||||
|
||||
// Test encryptKey(PrivateKey, Key, String, ...) with provider and null random
|
||||
e = EncryptedPrivateKeyInfo.encryptKey(priKey, key, ekpi.getAlgName(),
|
||||
ap.getParameterSpec(PBEParameterSpec.class), p, null);
|
||||
e = EncryptedPrivateKeyInfo.encrypt(priKey, key, ekpi.getAlgName(),
|
||||
ap.getParameterSpec(PBEParameterSpec.class), p, null);
|
||||
if (!Arrays.equals(ekpi.getEncryptedData(), e.getEncryptedData())) {
|
||||
throw new AssertionError("encryptKey() didn't match" +
|
||||
" with expected.");
|
||||
" with expected.");
|
||||
}
|
||||
|
||||
// Test encryptKey(PrivateKey, Key, String, ...) with provider and SecureRandom
|
||||
e = EncryptedPrivateKeyInfo.encryptKey(priKey, key, ekpi.getAlgName(),
|
||||
ap.getParameterSpec(PBEParameterSpec.class), p, new SecureRandom());
|
||||
e = EncryptedPrivateKeyInfo.encrypt(priKey, key, ekpi.getAlgName(),
|
||||
ap.getParameterSpec(PBEParameterSpec.class), p, new SecureRandom());
|
||||
if (!Arrays.equals(ekpi.getEncryptedData(), e.getEncryptedData())) {
|
||||
throw new AssertionError("encryptKey() didn't match" +
|
||||
" with expected.");
|
||||
" with expected.");
|
||||
}
|
||||
|
||||
// Test encryptKey(PrivateKey, Key, String, ...) with provider and null algorithm
|
||||
e = EncryptedPrivateKeyInfo.encryptKey(priKey, key, null, null,
|
||||
p, new SecureRandom());
|
||||
e = EncryptedPrivateKeyInfo.encrypt(priKey, key, Pem.DEFAULT_ALGO, null,
|
||||
p, new SecureRandom());
|
||||
assertEquals(e.getAlgName(), Pem.DEFAULT_ALGO);
|
||||
|
||||
|
||||
SecretKey key2 = new SecretKeySpec("1234567890123456".getBytes(), "AES");
|
||||
|
||||
// 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);
|
||||
assertEquals(key3, priKey, "AES encryption failed");
|
||||
}
|
||||
}
|
||||
@ -71,7 +71,8 @@ public class GetKey {
|
||||
passwdText.getBytes(), "PBE");
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
Provider p = Security.getProvider(System.getProperty("test.provider.name", "SunJCE"));
|
||||
Provider p = Security.getProvider(
|
||||
System.getProperty("test.provider.name", "SunJCE"));
|
||||
|
||||
EncryptedPrivateKeyInfo ekpi = PEMDecoder.of().decode(encEdECKey,
|
||||
EncryptedPrivateKeyInfo.class);
|
||||
|
||||
120
test/jdk/javax/crypto/EncryptedPrivateKeyInfo/GetKeyPair.java
Normal file
120
test/jdk/javax/crypto/EncryptedPrivateKeyInfo/GetKeyPair.java
Normal file
@ -0,0 +1,120 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @bug 8360563
|
||||
* @library /test/lib
|
||||
* @summary Testing getKeyPair using ML-KEM
|
||||
* @enablePreview
|
||||
* @modules java.base/sun.security.util
|
||||
*/
|
||||
|
||||
import jdk.test.lib.Asserts;
|
||||
import sun.security.util.DerValue;
|
||||
import javax.crypto.EncryptedPrivateKeyInfo;
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.security.*;
|
||||
|
||||
/*
|
||||
* This generates an ML-KEM key pair and makes it into PEM data. By using
|
||||
* PEM, it constructs a OneAsymmetricKey structure that combines
|
||||
* the public key into the private key encoding. Decode the PEM data into
|
||||
* a KeyPair and an EKPI for verification.
|
||||
*
|
||||
* The original private key does not have the public key encapsulated, so it
|
||||
* cannot be used for verification.
|
||||
*
|
||||
* Verify the decoded PEM KeyPair and EKPI.getKeyPair() return matching public
|
||||
* and private keys encodings; as well as, verify the original public key
|
||||
* matches.
|
||||
*/
|
||||
|
||||
public class GetKeyPair {
|
||||
private static final String passwdText = "fish";
|
||||
private static final char[] password = passwdText.toCharArray();
|
||||
private static final SecretKey key = new SecretKeySpec(
|
||||
passwdText.getBytes(), "PBE");
|
||||
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();
|
||||
keyOrigPriv = getPrivateKey(kpOrig.getPrivate());
|
||||
|
||||
// Encode the KeyPair into PEM, constructing an OneAsymmetricKey encoding
|
||||
String pem = PEMEncoder.of().withEncryption(password).
|
||||
encodeToString(kpOrig);
|
||||
// Decode to a KeyPair from the generated PEM for verification.
|
||||
KeyPair mlkemKP = PEMDecoder.of().withDecryption(password).
|
||||
decode(pem, KeyPair.class);
|
||||
|
||||
// Check decoded public key pair with original.
|
||||
Asserts.assertEqualsByteArray(mlkemKP.getPublic().getEncoded(),
|
||||
keyOrigPub, "Initial PublicKey compare didn't match.");
|
||||
byte[] priv = getPrivateKey(mlkemKP.getPrivate());
|
||||
Asserts.assertEqualsByteArray(priv, keyOrigPriv,
|
||||
"Initial PrivateKey compare didn't match");
|
||||
|
||||
// Decode to a EncryptedPrivateKeyInfo.
|
||||
EncryptedPrivateKeyInfo ekpi = PEMDecoder.of().decode(pem,
|
||||
EncryptedPrivateKeyInfo.class);
|
||||
|
||||
// Test getKeyPair(password)
|
||||
System.out.print("Testing getKeyPair(char[]): ");
|
||||
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));
|
||||
}
|
||||
|
||||
static void arrayCheck(KeyPair kp) throws Exception {
|
||||
Asserts.assertEqualsByteArray(getPrivateKey(kp.getPrivate()), keyOrigPriv,
|
||||
"PrivateKey didn't match with expected.");
|
||||
Asserts.assertEqualsByteArray(kp.getPublic().getEncoded(), keyOrigPub,
|
||||
"PublicKey didn't match with expected.");
|
||||
System.out.println("PASS");
|
||||
}
|
||||
|
||||
static byte[] getPrivateKey(PrivateKey p) throws Exception{
|
||||
var val = new DerValue(p.getEncoded());
|
||||
// Get version
|
||||
val.data.getInteger();
|
||||
// Get AlgorithmID
|
||||
val.data.getDerValue();
|
||||
// Return PrivateKey
|
||||
return val.data.getOctetString();
|
||||
}
|
||||
}
|
||||
@ -31,7 +31,6 @@ import javax.net.ssl.TrustManagerFactory;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.security.KeyStore;
|
||||
import java.security.PEMDecoder;
|
||||
import java.security.PEMRecord;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.X509Certificate;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user