8298420: Implement JEP 470: PEM Encodings of Cryptographic Objects (Preview)
Reviewed-by: weijun, mr, mullan, jnimeh
This commit is contained in:
parent
28f509317d
commit
bb2c80c0e9
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2023, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -34,7 +34,7 @@ import java.security.spec.AlgorithmParameterSpec;
|
||||
*
|
||||
* @since 22
|
||||
*/
|
||||
public interface AsymmetricKey extends Key {
|
||||
public non-sealed interface AsymmetricKey extends Key, DEREncodable {
|
||||
/**
|
||||
* Returns the parameters associated with this key.
|
||||
* The parameters are optional and may be either
|
||||
|
59
src/java.base/share/classes/java/security/DEREncodable.java
Normal file
59
src/java.base/share/classes/java/security/DEREncodable.java
Normal file
@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package java.security;
|
||||
|
||||
import jdk.internal.javac.PreviewFeature;
|
||||
|
||||
import javax.crypto.EncryptedPrivateKeyInfo;
|
||||
import java.security.cert.X509CRL;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.security.spec.PKCS8EncodedKeySpec;
|
||||
import java.security.spec.X509EncodedKeySpec;
|
||||
|
||||
/**
|
||||
* This interface is implemented by security API classes that contain
|
||||
* binary-encodable key or certificate material.
|
||||
* These APIs or their subclasses typically provide methods to convert
|
||||
* their instances to and from byte arrays in the Distinguished
|
||||
* Encoding Rules (DER) format.
|
||||
*
|
||||
* @see AsymmetricKey
|
||||
* @see KeyPair
|
||||
* @see PKCS8EncodedKeySpec
|
||||
* @see X509EncodedKeySpec
|
||||
* @see EncryptedPrivateKeyInfo
|
||||
* @see X509Certificate
|
||||
* @see X509CRL
|
||||
* @see PEMRecord
|
||||
*
|
||||
* @since 25
|
||||
*/
|
||||
|
||||
@PreviewFeature(feature = PreviewFeature.Feature.PEM_API)
|
||||
public sealed interface DEREncodable permits AsymmetricKey, KeyPair,
|
||||
PKCS8EncodedKeySpec, X509EncodedKeySpec, EncryptedPrivateKeyInfo,
|
||||
X509Certificate, X509CRL, PEMRecord {
|
||||
}
|
@ -37,7 +37,7 @@ package java.security;
|
||||
* @since 1.1
|
||||
*/
|
||||
|
||||
public final class KeyPair implements java.io.Serializable {
|
||||
public final class KeyPair implements java.io.Serializable, DEREncodable {
|
||||
|
||||
@java.io.Serial
|
||||
private static final long serialVersionUID = -7565189502268009837L;
|
||||
|
512
src/java.base/share/classes/java/security/PEMDecoder.java
Normal file
512
src/java.base/share/classes/java/security/PEMDecoder.java
Normal file
@ -0,0 +1,512 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package java.security;
|
||||
|
||||
import jdk.internal.javac.PreviewFeature;
|
||||
|
||||
import sun.security.pkcs.PKCS8Key;
|
||||
import sun.security.rsa.RSAPrivateCrtKeyImpl;
|
||||
import sun.security.util.KeyUtil;
|
||||
import sun.security.util.Pem;
|
||||
|
||||
import javax.crypto.EncryptedPrivateKeyInfo;
|
||||
import javax.crypto.spec.PBEKeySpec;
|
||||
import java.io.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.cert.*;
|
||||
import java.security.spec.*;
|
||||
import java.util.Base64;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* {@code PEMDecoder} implements a decoder for Privacy-Enhanced Mail (PEM) data.
|
||||
* PEM is a textual encoding used to store and transfer security
|
||||
* objects, such as asymmetric keys, certificates, and certificate revocation
|
||||
* lists (CRLs). It is defined in RFC 1421 and RFC 7468. PEM consists of a
|
||||
* Base64-formatted binary encoding enclosed by a type-identifying header
|
||||
* and footer.
|
||||
*
|
||||
* <p> The {@linkplain #decode(String)} and {@linkplain #decode(InputStream)}
|
||||
* methods return an instance of a class that matches the data
|
||||
* type and implements {@link DEREncodable}.
|
||||
*
|
||||
* <p> The following lists the supported PEM types and the {@code DEREncodable}
|
||||
* types that each are decoded as:
|
||||
* <ul>
|
||||
* <li>CERTIFICATE : {@code X509Certificate}</li>
|
||||
* <li>X509 CRL : {@code X509CRL}</li>
|
||||
* <li>PUBLIC KEY : {@code PublicKey}</li>
|
||||
* <li>PUBLIC KEY : {@code X509EncodedKeySpec} (Only supported when passed as
|
||||
* a {@code Class} parameter)</li>
|
||||
* <li>PRIVATE KEY : {@code PrivateKey}</li>
|
||||
* <li>PRIVATE KEY : {@code PKCS8EncodedKeySpec} (Only supported when passed
|
||||
* as a {@code Class} parameter)</li>
|
||||
* <li>PRIVATE KEY : {@code KeyPair} (if the encoding also contains a
|
||||
* public key)</li>
|
||||
* <li>ENCRYPTED PRIVATE KEY : {@code EncryptedPrivateKeyInfo} </li>
|
||||
* <li>ENCRYPTED PRIVATE KEY : {@code PrivateKey} (if configured with
|
||||
* Decryption)</li>
|
||||
* <li>Other types : {@code PEMRecord} </li>
|
||||
* </ul>
|
||||
*
|
||||
* <p> The {@code PublicKey} and {@code PrivateKey} types, an algorithm specific
|
||||
* subclass is returned if the underlying algorithm is supported. For example an
|
||||
* ECPublicKey and ECPrivateKey for Elliptic Curve keys.
|
||||
*
|
||||
* <p> If the PEM type does not have a corresponding class,
|
||||
* {@code decode(String)} and {@code decode(InputStream)} will return a
|
||||
* {@link PEMRecord}.
|
||||
*
|
||||
* <p> The {@linkplain #decode(String, Class)} and
|
||||
* {@linkplain #decode(InputStream, Class)} methods take a Class parameter
|
||||
* which determines the type of {@code DEREncodable} that is returned. These
|
||||
* methods are useful when extracting or changing the return class.
|
||||
* For example, if the PEM contains both public and private keys, the
|
||||
* Class parameter can specify which to return. Use
|
||||
* {@code PrivateKey.class} to return only the private key.
|
||||
* If the Class parameter is set to {@code X509EncodedKeySpec.class}, the
|
||||
* public key will be returned in that format. Any type of PEM data can be
|
||||
* decoded into a {@code PEMRecord} by specifying {@code PEMRecord.class}.
|
||||
* If the Class parameter doesn't match the PEM content, an
|
||||
* {@code IllegalArgumentException} will be thrown.
|
||||
*
|
||||
* <p> A new {@code PEMDecoder} instance is created when configured
|
||||
* with {@linkplain #withFactory(Provider)} and/or
|
||||
* {@linkplain #withDecryption(char[])}. {@linkplain #withFactory(Provider)}
|
||||
* configures the decoder to use only {@linkplain KeyFactory} and
|
||||
* {@linkplain CertificateFactory} instances from the given {@code Provider}.
|
||||
* {@link#withDecryption(char[])} configures the decoder to decrypt all
|
||||
* encrypted private key PEM data using the given password.
|
||||
* Configuring an instance for decryption does not prevent decoding with
|
||||
* unencrypted PEM. Any encrypted PEM that fails decryption
|
||||
* will throw a {@link RuntimeException}. When an encrypted private key PEM is
|
||||
* used with a decoder not configured for decryption, an
|
||||
* {@link EncryptedPrivateKeyInfo} object is returned.
|
||||
*
|
||||
* <p>This class is immutable and thread-safe.
|
||||
*
|
||||
* <p> Here is an example of decoding a {@code PrivateKey} object:
|
||||
* {@snippet lang = java:
|
||||
* PEMDecoder pd = PEMDecoder.of();
|
||||
* PrivateKey priKey = pd.decode(priKeyPEM, PrivateKey.class);
|
||||
* }
|
||||
*
|
||||
* <p> Here is an example of a {@code PEMDecoder} configured with decryption
|
||||
* and a factory provider:
|
||||
* {@snippet lang = java:
|
||||
* PEMDecoder pe = PEMDecoder.of().withDecryption(password).
|
||||
* withFactory(provider);
|
||||
* byte[] pemData = pe.decode(privKey);
|
||||
* }
|
||||
*
|
||||
* @implNote An implementation may support other PEM types and
|
||||
* {@code DEREncodables}. This implementation additionally supports PEM types:
|
||||
* {@code X509 CERTIFICATE}, {@code X.509 CERTIFICATE}, {@code CRL},
|
||||
* and {@code RSA PRIVATE KEY}.
|
||||
*
|
||||
* @see PEMEncoder
|
||||
* @see PEMRecord
|
||||
* @see EncryptedPrivateKeyInfo
|
||||
*
|
||||
* @spec https://www.rfc-editor.org/info/rfc1421
|
||||
* RFC 1421: Privacy Enhancement for Internet Electronic Mail
|
||||
* @spec https://www.rfc-editor.org/info/rfc7468
|
||||
* RFC 7468: Textual Encodings of PKIX, PKCS, and CMS Structures
|
||||
*
|
||||
* @since 25
|
||||
*/
|
||||
|
||||
@PreviewFeature(feature = PreviewFeature.Feature.PEM_API)
|
||||
public final class PEMDecoder {
|
||||
private final Provider factory;
|
||||
private final PBEKeySpec password;
|
||||
|
||||
// Singleton instance for PEMDecoder
|
||||
private final static PEMDecoder PEM_DECODER = new PEMDecoder(null, null);
|
||||
|
||||
/**
|
||||
* Creates an instance with a specific KeyFactory and/or password.
|
||||
* @param withFactory KeyFactory provider
|
||||
* @param withPassword char[] password for EncryptedPrivateKeyInfo
|
||||
* decryption
|
||||
*/
|
||||
private PEMDecoder(Provider withFactory, PBEKeySpec withPassword) {
|
||||
password = withPassword;
|
||||
factory = withFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an instance of {@code PEMDecoder}.
|
||||
*
|
||||
* @return a {@code PEMDecoder} instance
|
||||
*/
|
||||
public static PEMDecoder of() {
|
||||
return PEM_DECODER;
|
||||
}
|
||||
|
||||
/**
|
||||
* After the header, footer, and base64 have been separated, identify the
|
||||
* header and footer and proceed with decoding the base64 for the
|
||||
* appropriate type.
|
||||
*/
|
||||
private DEREncodable decode(PEMRecord pem) {
|
||||
Base64.Decoder decoder = Base64.getMimeDecoder();
|
||||
|
||||
try {
|
||||
return switch (pem.type()) {
|
||||
case Pem.PUBLIC_KEY -> {
|
||||
X509EncodedKeySpec spec =
|
||||
new X509EncodedKeySpec(decoder.decode(pem.pem()));
|
||||
yield getKeyFactory(
|
||||
KeyUtil.getAlgorithm(spec.getEncoded())).
|
||||
generatePublic(spec);
|
||||
}
|
||||
case Pem.PRIVATE_KEY -> {
|
||||
PKCS8Key p8key = new PKCS8Key(decoder.decode(pem.pem()));
|
||||
String algo = p8key.getAlgorithm();
|
||||
KeyFactory kf = getKeyFactory(algo);
|
||||
DEREncodable d = kf.generatePrivate(
|
||||
new PKCS8EncodedKeySpec(p8key.getEncoded(), algo));
|
||||
|
||||
// Look for a public key inside the pkcs8 encoding.
|
||||
if (p8key.getPubKeyEncoded() != null) {
|
||||
// Check if this is a OneAsymmetricKey encoding
|
||||
X509EncodedKeySpec spec = new X509EncodedKeySpec(
|
||||
p8key.getPubKeyEncoded(), algo);
|
||||
yield new KeyPair(getKeyFactory(algo).
|
||||
generatePublic(spec), (PrivateKey) d);
|
||||
|
||||
} else if (d instanceof PKCS8Key p8 &&
|
||||
p8.getPubKeyEncoded() != null) {
|
||||
// If the KeyFactory decoded an algorithm-specific
|
||||
// encodings, look for the public key again. This
|
||||
// happens with EC and SEC1-v2 encoding
|
||||
X509EncodedKeySpec spec = new X509EncodedKeySpec(
|
||||
p8.getPubKeyEncoded(), algo);
|
||||
yield new KeyPair(getKeyFactory(algo).
|
||||
generatePublic(spec), p8);
|
||||
} else {
|
||||
// No public key, return the private key.
|
||||
yield d;
|
||||
}
|
||||
}
|
||||
case Pem.ENCRYPTED_PRIVATE_KEY -> {
|
||||
if (password == null) {
|
||||
yield new EncryptedPrivateKeyInfo(decoder.decode(
|
||||
pem.pem()));
|
||||
}
|
||||
yield new EncryptedPrivateKeyInfo(decoder.decode(pem.pem())).
|
||||
getKey(password.getPassword());
|
||||
}
|
||||
case Pem.CERTIFICATE, Pem.X509_CERTIFICATE,
|
||||
Pem.X_509_CERTIFICATE -> {
|
||||
CertificateFactory cf = getCertFactory("X509");
|
||||
yield (X509Certificate) cf.generateCertificate(
|
||||
new ByteArrayInputStream(decoder.decode(pem.pem())));
|
||||
}
|
||||
case Pem.X509_CRL, Pem.CRL -> {
|
||||
CertificateFactory cf = getCertFactory("X509");
|
||||
yield (X509CRL) cf.generateCRL(
|
||||
new ByteArrayInputStream(decoder.decode(pem.pem())));
|
||||
}
|
||||
case Pem.RSA_PRIVATE_KEY -> {
|
||||
KeyFactory kf = getKeyFactory("RSA");
|
||||
yield kf.generatePrivate(
|
||||
RSAPrivateCrtKeyImpl.getKeySpec(decoder.decode(
|
||||
pem.pem())));
|
||||
}
|
||||
default -> pem;
|
||||
};
|
||||
} catch (GeneralSecurityException | IOException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes and returns a {@link DEREncodable} from the given {@code String}.
|
||||
*
|
||||
* <p> This method reads the {@code String} until PEM data is found
|
||||
* or the end of the {@code String} is reached. If no PEM data is found,
|
||||
* an {@code IllegalArgumentException} is thrown.
|
||||
*
|
||||
* <p> This method returns a Java API cryptographic object,
|
||||
* such as a {@code PrivateKey}, if the PEM type is supported.
|
||||
* Any non-PEM data preceding the PEM header is ignored by the decoder.
|
||||
* Otherwise, a {@link PEMRecord} will be returned containing
|
||||
* the type identifier and Base64-encoded data.
|
||||
* Any non-PEM data preceding the PEM header will be stored in
|
||||
* {@code leadingData}.
|
||||
*
|
||||
* <p> Input consumed by this method is read in as
|
||||
* {@link java.nio.charset.StandardCharsets#UTF_8 UTF-8}.
|
||||
*
|
||||
* @param str a String containing PEM data
|
||||
* @return a {@code DEREncodable}
|
||||
* @throws IllegalArgumentException on error in decoding or no PEM data
|
||||
* found
|
||||
* @throws NullPointerException when {@code str} is null
|
||||
*/
|
||||
public DEREncodable decode(String str) {
|
||||
Objects.requireNonNull(str);
|
||||
DEREncodable de;
|
||||
try {
|
||||
return decode(new ByteArrayInputStream(
|
||||
str.getBytes(StandardCharsets.UTF_8)));
|
||||
} catch (IOException e) {
|
||||
// With all data contained in the String, there are no IO ops.
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes and returns a {@link DEREncodable} from the given
|
||||
* {@code InputStream}.
|
||||
*
|
||||
* <p> This method reads from the {@code InputStream} until the end of
|
||||
* the PEM footer or the end of the stream. If an I/O error occurs,
|
||||
* the read position in the stream may become inconsistent.
|
||||
* It is recommended to perform no further decoding operations
|
||||
* on the {@code InputStream}.
|
||||
*
|
||||
* <p> This method returns a Java API cryptographic object,
|
||||
* such as a {@code PrivateKey}, if the PEM type is supported.
|
||||
* Any non-PEM data preceding the PEM header is ignored by the decoder.
|
||||
* Otherwise, a {@link PEMRecord} will be returned containing
|
||||
* the type identifier and Base64-encoded data.
|
||||
* Any non-PEM data preceding the PEM header will be stored in
|
||||
* {@code leadingData}.
|
||||
*
|
||||
* <p> If no PEM data is found, an {@code IllegalArgumentException} is
|
||||
* thrown.
|
||||
*
|
||||
* @param is InputStream containing PEM data
|
||||
* @return a {@code DEREncodable}
|
||||
* @throws IOException on IO or PEM syntax error where the
|
||||
* {@code InputStream} did not complete decoding.
|
||||
* @throws EOFException at the end of the {@code InputStream}
|
||||
* @throws IllegalArgumentException on error in decoding
|
||||
* @throws NullPointerException when {@code is} is null
|
||||
*/
|
||||
public DEREncodable decode(InputStream is) throws IOException {
|
||||
Objects.requireNonNull(is);
|
||||
PEMRecord pem = Pem.readPEM(is);
|
||||
return decode(pem);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes and returns a {@code DEREncodable} of the specified class from
|
||||
* the given PEM string. {@code tClass} must extend {@link DEREncodable}
|
||||
* and be an appropriate class for the PEM type.
|
||||
*
|
||||
* <p> This method reads the {@code String} until PEM data is found
|
||||
* or the end of the {@code String} is reached. If no PEM data is found,
|
||||
* an {@code IllegalArgumentException} is thrown.
|
||||
*
|
||||
* <p> If the class parameter is {@code PEMRecord.class},
|
||||
* a {@linkplain PEMRecord} is returned containing the
|
||||
* type identifier and Base64 encoding. Any non-PEM data preceding
|
||||
* the PEM header will be stored in {@code leadingData}. Other
|
||||
* class parameters will not return preceding non-PEM data.
|
||||
*
|
||||
* <p> Input consumed by this method is read in as
|
||||
* {@link java.nio.charset.StandardCharsets#UTF_8 UTF-8}.
|
||||
*
|
||||
* @param <S> Class type parameter that extends {@code DEREncodable}
|
||||
* @param str the String containing PEM data
|
||||
* @param tClass the returned object class that implements
|
||||
* {@code DEREncodable}
|
||||
* @return a {@code DEREncodable} specified by {@code tClass}
|
||||
* @throws IllegalArgumentException on error in decoding or no PEM data
|
||||
* found
|
||||
* @throws ClassCastException if {@code tClass} is invalid for the PEM type
|
||||
* @throws NullPointerException when any input values are null
|
||||
*/
|
||||
public <S extends DEREncodable> S decode(String str, Class<S> tClass) {
|
||||
Objects.requireNonNull(str);
|
||||
try {
|
||||
return decode(new ByteArrayInputStream(
|
||||
str.getBytes(StandardCharsets.UTF_8)), tClass);
|
||||
} catch (IOException e) {
|
||||
// With all data contained in the String, there are no IO ops.
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes and returns the specified class for the given
|
||||
* {@link InputStream}. The class must extend {@link DEREncodable} and be
|
||||
* an appropriate class for the PEM type.
|
||||
*
|
||||
* <p> This method reads from the {@code InputStream} until the end of
|
||||
* the PEM footer or the end of the stream. If an I/O error occurs,
|
||||
* the read position in the stream may become inconsistent.
|
||||
* It is recommended to perform no further decoding operations
|
||||
* on the {@code InputStream}.
|
||||
*
|
||||
* <p> If the class parameter is {@code PEMRecord.class},
|
||||
* a {@linkplain PEMRecord} is returned containing the
|
||||
* type identifier and Base64 encoding. Any non-PEM data preceding
|
||||
* the PEM header will be stored in {@code leadingData}. Other
|
||||
* class parameters will not return preceding non-PEM data.
|
||||
*
|
||||
* <p> If no PEM data is found, an {@code IllegalArgumentException} is
|
||||
* thrown.
|
||||
*
|
||||
* @param <S> Class type parameter that extends {@code DEREncodable}.
|
||||
* @param is an InputStream containing PEM data
|
||||
* @param tClass the returned object class that implements
|
||||
* {@code DEREncodable}.
|
||||
* @return a {@code DEREncodable} typecast to {@code tClass}
|
||||
* @throws IOException on IO or PEM syntax error where the
|
||||
* {@code InputStream} did not complete decoding.
|
||||
* @throws EOFException at the end of the {@code InputStream}
|
||||
* @throws IllegalArgumentException on error in decoding
|
||||
* @throws ClassCastException if {@code tClass} is invalid for the PEM type
|
||||
* @throws NullPointerException when any input values are null
|
||||
*
|
||||
* @see #decode(InputStream)
|
||||
* @see #decode(String, Class)
|
||||
*/
|
||||
public <S extends DEREncodable> S decode(InputStream is, Class<S> tClass)
|
||||
throws IOException {
|
||||
Objects.requireNonNull(is);
|
||||
Objects.requireNonNull(tClass);
|
||||
PEMRecord pem = Pem.readPEM(is);
|
||||
|
||||
if (tClass.isAssignableFrom(PEMRecord.class)) {
|
||||
return tClass.cast(pem);
|
||||
}
|
||||
DEREncodable so = decode(pem);
|
||||
|
||||
/*
|
||||
* If the object is a KeyPair, check if the tClass is set to class
|
||||
* specific to a private or public key. Because PKCS8v2 can be a
|
||||
* KeyPair, it is possible for someone to assume all their PEM private
|
||||
* keys are only PrivateKey and not KeyPair.
|
||||
*/
|
||||
if (so instanceof KeyPair kp) {
|
||||
if ((PrivateKey.class).isAssignableFrom(tClass) ||
|
||||
(PKCS8EncodedKeySpec.class).isAssignableFrom(tClass)) {
|
||||
so = kp.getPrivate();
|
||||
}
|
||||
if ((PublicKey.class).isAssignableFrom(tClass) ||
|
||||
(X509EncodedKeySpec.class).isAssignableFrom(tClass)) {
|
||||
so = kp.getPublic();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* KeySpec use getKeySpec after the Key has been generated. Even though
|
||||
* returning a binary encoding after the Base64 decoding is ok when the
|
||||
* user wants PKCS8EncodedKeySpec, generating the key verifies the
|
||||
* binary encoding and allows the KeyFactory to use the provider's
|
||||
* KeySpec()
|
||||
*/
|
||||
|
||||
if ((EncodedKeySpec.class).isAssignableFrom(tClass) &&
|
||||
so instanceof Key key) {
|
||||
try {
|
||||
// unchecked suppressed as we know tClass comes from KeySpec
|
||||
// KeyType not relevant here. We just want KeyFactory
|
||||
if ((PKCS8EncodedKeySpec.class).isAssignableFrom(tClass)) {
|
||||
so = getKeyFactory(key.getAlgorithm()).
|
||||
getKeySpec(key, PKCS8EncodedKeySpec.class);
|
||||
} else if ((X509EncodedKeySpec.class).isAssignableFrom(tClass)) {
|
||||
so = getKeyFactory(key.getAlgorithm())
|
||||
.getKeySpec(key, X509EncodedKeySpec.class);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Invalid KeySpec.");
|
||||
}
|
||||
} catch (InvalidKeySpecException e) {
|
||||
throw new IllegalArgumentException("Invalid KeySpec " +
|
||||
"specified (" + tClass.getName() +") for key (" +
|
||||
key.getClass().getName() +")", e);
|
||||
}
|
||||
}
|
||||
|
||||
return tClass.cast(so);
|
||||
}
|
||||
|
||||
private KeyFactory getKeyFactory(String algorithm) {
|
||||
if (algorithm == null || algorithm.isEmpty()) {
|
||||
throw new IllegalArgumentException("No algorithm found in " +
|
||||
"the encoding");
|
||||
}
|
||||
try {
|
||||
if (factory == null) {
|
||||
return KeyFactory.getInstance(algorithm);
|
||||
}
|
||||
return KeyFactory.getInstance(algorithm, factory);
|
||||
} catch (GeneralSecurityException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}
|
||||
|
||||
// Convenience method to avoid provider getInstance checks clutter
|
||||
private CertificateFactory getCertFactory(String algorithm) {
|
||||
try {
|
||||
if (factory == null) {
|
||||
return CertificateFactory.getInstance(algorithm);
|
||||
}
|
||||
return CertificateFactory.getInstance(algorithm, factory);
|
||||
} catch (GeneralSecurityException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy of this {@code PEMDecoder} instance that uses
|
||||
* {@link KeyFactory} and {@link CertificateFactory} implementations
|
||||
* from the specified {@link Provider} to produce cryptographic objects.
|
||||
* Any errors using the {@code Provider} will occur during decoding.
|
||||
*
|
||||
* <p>If {@code provider} is {@code null}, a new instance is returned with
|
||||
* the default provider configuration.
|
||||
*
|
||||
* @param provider the factory provider
|
||||
* @return a new PEMEncoder instance configured to the {@code Provider}.
|
||||
* @throws NullPointerException if {@code provider} is null
|
||||
*/
|
||||
public PEMDecoder withFactory(Provider provider) {
|
||||
Objects.requireNonNull(provider);
|
||||
return new PEMDecoder(provider, password);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy of this {@code PEMDecoder} that decodes and decrypts
|
||||
* encrypted private keys using the specified password.
|
||||
* Non-encrypted PEM can still be decoded from this instance.
|
||||
*
|
||||
* @param password the password to decrypt encrypted PEM data. This array
|
||||
* is cloned and stored in the new instance.
|
||||
* @return a new PEMEncoder instance configured for decryption
|
||||
* @throws NullPointerException if {@code password} is null
|
||||
*/
|
||||
public PEMDecoder withDecryption(char[] password) {
|
||||
Objects.requireNonNull(password);
|
||||
return new PEMDecoder(factory, new PBEKeySpec(password));
|
||||
}
|
||||
}
|
382
src/java.base/share/classes/java/security/PEMEncoder.java
Normal file
382
src/java.base/share/classes/java/security/PEMEncoder.java
Normal file
@ -0,0 +1,382 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package java.security;
|
||||
|
||||
import jdk.internal.javac.PreviewFeature;
|
||||
import sun.security.pkcs.PKCS8Key;
|
||||
import sun.security.util.DerOutputStream;
|
||||
import sun.security.util.DerValue;
|
||||
import sun.security.util.Pem;
|
||||
import sun.security.x509.AlgorithmId;
|
||||
|
||||
import javax.crypto.*;
|
||||
import javax.crypto.spec.PBEKeySpec;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.cert.*;
|
||||
import java.security.spec.AlgorithmParameterSpec;
|
||||
import java.security.spec.PKCS8EncodedKeySpec;
|
||||
import java.security.spec.X509EncodedKeySpec;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
/**
|
||||
* {@code PEMEncoder} implements an encoder for Privacy-Enhanced Mail (PEM)
|
||||
* data. PEM is a textual encoding used to store and transfer security
|
||||
* objects, such as asymmetric keys, certificates, and certificate revocation
|
||||
* lists (CRL). It is defined in RFC 1421 and RFC 7468. PEM consists of a
|
||||
* Base64-formatted binary encoding enclosed by a type-identifying header
|
||||
* and footer.
|
||||
*
|
||||
* <p> Encoding may be performed on Java API cryptographic objects that
|
||||
* implement {@link DEREncodable}. The {@link #encode(DEREncodable)}
|
||||
* and {@link #encodeToString(DEREncodable)} methods encode a DEREncodable
|
||||
* into PEM and return the data in a byte array or String.
|
||||
*
|
||||
* <p> Private keys can be encrypted and encoded by configuring a
|
||||
* {@code PEMEncoder} with the {@linkplain #withEncryption(char[])} method,
|
||||
* which takes a password and returns a new {@code PEMEncoder} instance
|
||||
* configured to encrypt the key with that password. Alternatively, a
|
||||
* private key encrypted as an {@code EncryptedKeyInfo} object can be encoded
|
||||
* directly to PEM by passing it to the {@code encode} or
|
||||
* {@code encodeToString} methods.
|
||||
*
|
||||
* <p> PKCS #8 2.0 defines the ASN.1 OneAsymmetricKey structure, which may
|
||||
* contain both private and public keys.
|
||||
* {@link KeyPair} objects passed to the {@code encode} or
|
||||
* {@code encodeToString} methods are encoded as a
|
||||
* OneAsymmetricKey structure using the "PRIVATE KEY" type.
|
||||
*
|
||||
* <p> When encoding a {@link PEMRecord}, the API surrounds the
|
||||
* {@linkplain PEMRecord#pem()} with the PEM header and footer
|
||||
* from {@linkplain PEMRecord#type()}. {@linkplain PEMRecord#leadingData()} is
|
||||
* not included in the encoding. {@code PEMRecord} will not perform
|
||||
* validity checks on the data.
|
||||
*
|
||||
* <p>The following lists the supported {@code DEREncodable} classes and
|
||||
* the PEM types that each are encoded as:
|
||||
*
|
||||
* <ul>
|
||||
* <li>{@code X509Certificate} : CERTIFICATE</li>
|
||||
* <li>{@code X509CRL} : X509 CRL</li>
|
||||
* <li>{@code PublicKey}: PUBLIC KEY</li>
|
||||
* <li>{@code PrivateKey} : PRIVATE KEY</li>
|
||||
* <li>{@code PrivateKey} (if configured with encryption):
|
||||
* ENCRYPTED PRIVATE KEY</li>
|
||||
* <li>{@code EncryptedPrivateKeyInfo} : ENCRYPTED PRIVATE KEY</li>
|
||||
* <li>{@code KeyPair} : PRIVATE KEY</li>
|
||||
* <li>{@code X509EncodedKeySpec} : PUBLIC KEY</li>
|
||||
* <li>{@code PKCS8EncodedKeySpec} : PRIVATE KEY</li>
|
||||
* <li>{@code PEMRecord} : {@code PEMRecord.type()}</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p> This class is immutable and thread-safe.
|
||||
*
|
||||
* <p> Here is an example of encoding a {@code PrivateKey} object:
|
||||
* {@snippet lang = java:
|
||||
* PEMEncoder pe = PEMEncoder.of();
|
||||
* byte[] pemData = pe.encode(privKey);
|
||||
* }
|
||||
*
|
||||
* <p> Here is an example that encrypts and encodes a private key using the
|
||||
* specified password:
|
||||
* {@snippet lang = java:
|
||||
* PEMEncoder pe = PEMEncoder.of().withEncryption(password);
|
||||
* byte[] pemData = pe.encode(privKey);
|
||||
* }
|
||||
*
|
||||
* @implNote An implementation may support other PEM types and DEREncodables.
|
||||
*
|
||||
*
|
||||
* @see PEMDecoder
|
||||
* @see PEMRecord
|
||||
* @see EncryptedPrivateKeyInfo
|
||||
*
|
||||
* @spec https://www.rfc-editor.org/info/rfc1421
|
||||
* RFC 1421: Privacy Enhancement for Internet Electronic Mail
|
||||
* @spec https://www.rfc-editor.org/info/rfc7468
|
||||
* RFC 7468: Textual Encodings of PKIX, PKCS, and CMS Structures
|
||||
*
|
||||
* @since 25
|
||||
*/
|
||||
@PreviewFeature(feature = PreviewFeature.Feature.PEM_API)
|
||||
public final class PEMEncoder {
|
||||
|
||||
// Singleton instance of PEMEncoder
|
||||
private static final PEMEncoder PEM_ENCODER = new PEMEncoder(null);
|
||||
|
||||
// Stores the password for an encrypted encoder that isn't setup yet.
|
||||
private PBEKeySpec keySpec;
|
||||
// Stores the key after the encoder is ready to encrypt. The prevents
|
||||
// repeated SecretKeyFactory calls if the encoder is used on multiple keys.
|
||||
private SecretKey key;
|
||||
// Makes SecretKeyFactory generation thread-safe.
|
||||
private final ReentrantLock lock;
|
||||
|
||||
/**
|
||||
* Instantiate a {@code PEMEncoder} for Encrypted Private Keys.
|
||||
*
|
||||
* @param pbe contains the password spec used for encryption.
|
||||
*/
|
||||
private PEMEncoder(PBEKeySpec pbe) {
|
||||
keySpec = pbe;
|
||||
key = null;
|
||||
lock = new ReentrantLock();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an instance of {@code PEMEncoder}.
|
||||
*
|
||||
* @return a {@code PEMEncoder}
|
||||
*/
|
||||
public static PEMEncoder of() {
|
||||
return PEM_ENCODER;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes the specified {@code DEREncodable} and returns a PEM encoded
|
||||
* string.
|
||||
*
|
||||
* @param de the {@code DEREncodable} to be encoded
|
||||
* @return a {@code String} containing the PEM encoded data
|
||||
* @throws IllegalArgumentException if the {@code DEREncodable} cannot be
|
||||
* encoded
|
||||
* @throws NullPointerException if {@code de} is {@code null}
|
||||
* @see #withEncryption(char[])
|
||||
*/
|
||||
public String encodeToString(DEREncodable de) {
|
||||
Objects.requireNonNull(de);
|
||||
return switch (de) {
|
||||
case PublicKey pu -> buildKey(null, pu.getEncoded());
|
||||
case PrivateKey pr -> buildKey(pr.getEncoded(), null);
|
||||
case KeyPair kp -> {
|
||||
if (kp.getPublic() == null) {
|
||||
throw new IllegalArgumentException("KeyPair does not " +
|
||||
"contain PublicKey.");
|
||||
}
|
||||
if (kp.getPrivate() == null) {
|
||||
throw new IllegalArgumentException("KeyPair does not " +
|
||||
"contain PrivateKey.");
|
||||
}
|
||||
yield buildKey(kp.getPrivate().getEncoded(),
|
||||
kp.getPublic().getEncoded());
|
||||
}
|
||||
case X509EncodedKeySpec x ->
|
||||
buildKey(null, x.getEncoded());
|
||||
case PKCS8EncodedKeySpec p ->
|
||||
buildKey(p.getEncoded(), null);
|
||||
case EncryptedPrivateKeyInfo epki -> {
|
||||
try {
|
||||
yield Pem.pemEncoded(Pem.ENCRYPTED_PRIVATE_KEY,
|
||||
epki.getEncoded());
|
||||
} catch (IOException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}
|
||||
case X509Certificate c -> {
|
||||
try {
|
||||
if (isEncrypted()) {
|
||||
throw new IllegalArgumentException("Certificates " +
|
||||
"cannot be encrypted");
|
||||
}
|
||||
yield Pem.pemEncoded(Pem.CERTIFICATE, c.getEncoded());
|
||||
} catch (CertificateEncodingException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}
|
||||
case X509CRL crl -> {
|
||||
try {
|
||||
if (isEncrypted()) {
|
||||
throw new IllegalArgumentException("CRLs cannot be " +
|
||||
"encrypted");
|
||||
}
|
||||
yield Pem.pemEncoded(Pem.X509_CRL, crl.getEncoded());
|
||||
} catch (CRLException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}
|
||||
case PEMRecord rec -> {
|
||||
if (isEncrypted()) {
|
||||
throw new IllegalArgumentException("PEMRecord cannot be " +
|
||||
"encrypted");
|
||||
}
|
||||
yield Pem.pemEncoded(rec);
|
||||
}
|
||||
|
||||
default -> throw new IllegalArgumentException("PEM does not " +
|
||||
"support " + de.getClass().getCanonicalName());
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes the specified {@code DEREncodable} and returns the PEM encoding
|
||||
* in a byte array.
|
||||
*
|
||||
* @param de the {@code DEREncodable} to be encoded
|
||||
* @return a PEM encoded byte array
|
||||
* @throws IllegalArgumentException if the {@code DEREncodable} cannot be
|
||||
* encoded
|
||||
* @throws NullPointerException if {@code de} is {@code null}
|
||||
* @see #withEncryption(char[])
|
||||
*/
|
||||
public byte[] encode(DEREncodable de) {
|
||||
return encodeToString(de).getBytes(StandardCharsets.ISO_8859_1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new {@code PEMEncoder} instance configured for encryption
|
||||
* with the default algorithm and a given password.
|
||||
*
|
||||
* <p> Only {@link PrivateKey} objects can be encrypted with this newly
|
||||
* configured instance. Encoding other {@link DEREncodable} objects will
|
||||
* throw an {@code IllegalArgumentException}.
|
||||
*
|
||||
* @implNote
|
||||
* The default password-based encryption algorithm is defined
|
||||
* by the {@code jdk.epkcs8.defaultAlgorithm} security property and
|
||||
* uses the default encryption parameters of the provider that is selected.
|
||||
* For greater flexibility with encryption options and parameters, use
|
||||
* {@link EncryptedPrivateKeyInfo#encryptKey(PrivateKey, Key,
|
||||
* String, AlgorithmParameterSpec, Provider, SecureRandom)} and use the
|
||||
* returned object with {@link #encode(DEREncodable)}.
|
||||
*
|
||||
* @param password the encryption password. The array is cloned and
|
||||
* stored in the new instance.
|
||||
* @return a new {@code PEMEncoder} instance configured for encryption
|
||||
* @throws NullPointerException when password is {@code null}
|
||||
*/
|
||||
public PEMEncoder withEncryption(char[] password) {
|
||||
// PBEKeySpec clones the password
|
||||
Objects.requireNonNull(password, "password cannot be null.");
|
||||
return new PEMEncoder(new PBEKeySpec(password));
|
||||
}
|
||||
|
||||
/**
|
||||
* Build PEM encoding.
|
||||
*/
|
||||
private String buildKey(byte[] privateBytes, byte[] publicBytes) {
|
||||
DerOutputStream out = new DerOutputStream();
|
||||
Cipher cipher;
|
||||
|
||||
if (privateBytes == null && publicBytes == null) {
|
||||
throw new IllegalArgumentException("No encoded data given by the " +
|
||||
"DEREncodable.");
|
||||
}
|
||||
|
||||
// If `keySpec` is non-null, then `key` hasn't been established.
|
||||
// Setting a `key' prevents repeated key generations operations.
|
||||
// withEncryption() is a configuration method and cannot throw an
|
||||
// exception; therefore generation is delayed.
|
||||
if (keySpec != null) {
|
||||
// For thread safety
|
||||
lock.lock();
|
||||
if (key == null) {
|
||||
try {
|
||||
key = SecretKeyFactory.getInstance(Pem.DEFAULT_ALGO).
|
||||
generateSecret(keySpec);
|
||||
keySpec.clearPassword();
|
||||
keySpec = null;
|
||||
} catch (GeneralSecurityException e) {
|
||||
throw new IllegalArgumentException("Security property " +
|
||||
"\"jdk.epkcs8.defaultAlgorithm\" may not specify a " +
|
||||
"valid algorithm. Operation cannot be performed.", e);
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
} else {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
// If `key` is non-null, this is an encoder ready to encrypt.
|
||||
if (key != null) {
|
||||
if (privateBytes == null || publicBytes != null) {
|
||||
throw new IllegalArgumentException("Can only encrypt a " +
|
||||
"PrivateKey.");
|
||||
}
|
||||
|
||||
try {
|
||||
cipher = Cipher.getInstance(Pem.DEFAULT_ALGO);
|
||||
cipher.init(Cipher.ENCRYPT_MODE, key);
|
||||
} catch (GeneralSecurityException e) {
|
||||
throw new IllegalArgumentException("Security property " +
|
||||
"\"jdk.epkcs8.defaultAlgorithm\" may not specify a " +
|
||||
"valid algorithm. Operation cannot be performed.", e);
|
||||
}
|
||||
|
||||
try {
|
||||
new AlgorithmId(Pem.getPBEID(Pem.DEFAULT_ALGO),
|
||||
cipher.getParameters()).encode(out);
|
||||
out.putOctetString(cipher.doFinal(privateBytes));
|
||||
return Pem.pemEncoded(Pem.ENCRYPTED_PRIVATE_KEY,
|
||||
DerValue.wrap(DerValue.tag_Sequence, out).toByteArray());
|
||||
} catch (GeneralSecurityException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}
|
||||
|
||||
// X509 only
|
||||
if (publicBytes != null && privateBytes == null) {
|
||||
if (publicBytes.length == 0) {
|
||||
throw new IllegalArgumentException("No public key encoding " +
|
||||
"given by the DEREncodable.");
|
||||
}
|
||||
|
||||
return Pem.pemEncoded(Pem.PUBLIC_KEY, publicBytes);
|
||||
}
|
||||
|
||||
// PKCS8 only
|
||||
if (publicBytes == null && privateBytes != null) {
|
||||
if (privateBytes.length == 0) {
|
||||
throw new IllegalArgumentException("No private key encoding " +
|
||||
"given by the DEREncodable.");
|
||||
}
|
||||
|
||||
return Pem.pemEncoded(Pem.PRIVATE_KEY, privateBytes);
|
||||
}
|
||||
|
||||
// OneAsymmetricKey
|
||||
if (privateBytes.length == 0) {
|
||||
throw new IllegalArgumentException("No private key encoding " +
|
||||
"given by the DEREncodable.");
|
||||
}
|
||||
|
||||
if (publicBytes.length == 0) {
|
||||
throw new IllegalArgumentException("No public key encoding " +
|
||||
"given by the DEREncodable.");
|
||||
}
|
||||
try {
|
||||
return Pem.pemEncoded(Pem.PRIVATE_KEY,
|
||||
PKCS8Key.getEncoded(publicBytes, privateBytes));
|
||||
} catch (IOException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isEncrypted() {
|
||||
return (key != null || keySpec != null);
|
||||
}
|
||||
}
|
136
src/java.base/share/classes/java/security/PEMRecord.java
Normal file
136
src/java.base/share/classes/java/security/PEMRecord.java
Normal file
@ -0,0 +1,136 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package java.security;
|
||||
|
||||
import jdk.internal.javac.PreviewFeature;
|
||||
|
||||
import sun.security.util.Pem;
|
||||
|
||||
import java.util.Base64;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* {@code PEMRecord} is a {@link DEREncodable} that represents Privacy-Enhanced
|
||||
* Mail (PEM) data by its type and Base64 form. {@link PEMDecoder} and
|
||||
* {@link PEMEncoder} use {@code PEMRecord} when representing the data as a
|
||||
* cryptographic object is not desired or the type has no
|
||||
* {@code DEREncodable}.
|
||||
*
|
||||
* <p> {@code type} and {@code pem} may not be {@code null}.
|
||||
* {@code leadingData} may be null if no non-PEM data preceded PEM header
|
||||
* during decoding. {@code leadingData} may be useful for reading metadata
|
||||
* that accompanies PEM data.
|
||||
*
|
||||
* <p> No validation is performed during instantiation to ensure that
|
||||
* {@code type} conforms to {@code RFC 7468}, that {@code pem} is valid Base64,
|
||||
* or that {@code pem} matches the {@code type}. {@code leadingData} is not
|
||||
* defensively copied and does not return a clone when
|
||||
* {@linkplain #leadingData()} is called.
|
||||
*
|
||||
* @param type the type identifier in the PEM header without PEM syntax labels.
|
||||
* For a public key, {@code type} would be "PUBLIC KEY".
|
||||
* @param pem any data between the PEM header and footer.
|
||||
* @param leadingData any non-PEM data preceding the PEM header when decoding.
|
||||
*
|
||||
* @spec https://www.rfc-editor.org/info/rfc7468
|
||||
* RFC 7468: Textual Encodings of PKIX, PKCS, and CMS Structures
|
||||
*
|
||||
* @see PEMDecoder
|
||||
* @see PEMEncoder
|
||||
*
|
||||
* @since 25
|
||||
*/
|
||||
@PreviewFeature(feature = PreviewFeature.Feature.PEM_API)
|
||||
public record PEMRecord(String type, String pem, byte[] leadingData)
|
||||
implements DEREncodable {
|
||||
|
||||
/**
|
||||
* Creates a {@code PEMRecord} instance with the given parameters.
|
||||
*
|
||||
* @param type the type identifier
|
||||
* @param pem the Base64-encoded data encapsulated by the PEM header and
|
||||
* footer.
|
||||
* @param leadingData any non-PEM data read during the decoding process
|
||||
* before the PEM header. This value maybe {@code null}.
|
||||
* @throws IllegalArgumentException if the {@code type} is incorrectly
|
||||
* formatted.
|
||||
* @throws NullPointerException if {@code type} and/or {@code pem} are
|
||||
* {@code null}.
|
||||
*/
|
||||
public PEMRecord(String type, String pem, byte[] leadingData) {
|
||||
Objects.requireNonNull(type, "\"type\" cannot be null.");
|
||||
Objects.requireNonNull(pem, "\"pem\" cannot be null.");
|
||||
|
||||
// With no validity checking on `type`, the constructor accept anything
|
||||
// including lowercase. The onus is on the caller.
|
||||
if (type.startsWith("-") || type.startsWith("BEGIN ") ||
|
||||
type.startsWith("END ")) {
|
||||
throw new IllegalArgumentException("PEM syntax labels found. " +
|
||||
"Only the PEM type identifier is allowed");
|
||||
}
|
||||
|
||||
this.type = type;
|
||||
this.pem = pem;
|
||||
this.leadingData = leadingData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@code PEMRecord} instance with a given {@code type} and
|
||||
* {@code pem} data in String form. {@code leadingData} is set to null.
|
||||
*
|
||||
* @param type the PEM type identifier
|
||||
* @param pem the Base64-encoded data encapsulated by the PEM header and
|
||||
* footer.
|
||||
* @throws IllegalArgumentException if the {@code type} is incorrectly
|
||||
* formatted.
|
||||
* @throws NullPointerException if {@code type} and/or {@code pem} are
|
||||
* {@code null}.
|
||||
*/
|
||||
public PEMRecord(String type, String pem) {
|
||||
this(type, pem, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the binary encoding from the Base64 data contained in
|
||||
* {@code pem}.
|
||||
*
|
||||
* @throws IllegalArgumentException if {@code pem} cannot be decoded.
|
||||
* @return a new array of the binary encoding each time this
|
||||
* method is called.
|
||||
*/
|
||||
public byte[] getEncoded() {
|
||||
return Base64.getMimeDecoder().decode(pem);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the type and Base64 encoding in PEM format. {@code leadingData}
|
||||
* is not returned by this method.
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return Pem.pemEncoded(this);
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 1997, 2024, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -107,7 +107,7 @@ import java.util.Set;
|
||||
* @see X509Extension
|
||||
*/
|
||||
|
||||
public abstract class X509CRL extends CRL implements X509Extension {
|
||||
public abstract non-sealed class X509CRL extends CRL implements X509Extension, DEREncodable {
|
||||
|
||||
private transient X500Principal issuerPrincipal;
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 1997, 2024, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -107,8 +107,8 @@ import java.util.List;
|
||||
* @see X509Extension
|
||||
*/
|
||||
|
||||
public abstract class X509Certificate extends Certificate
|
||||
implements X509Extension {
|
||||
public abstract non-sealed class X509Certificate extends Certificate
|
||||
implements X509Extension, DEREncodable {
|
||||
|
||||
@java.io.Serial
|
||||
private static final long serialVersionUID = -2491127588187038216L;
|
||||
|
@ -25,25 +25,35 @@
|
||||
|
||||
package java.security.spec;
|
||||
|
||||
import java.security.DEREncodable;
|
||||
|
||||
/**
|
||||
* This class represents the ASN.1 encoding of a private key,
|
||||
* encoded according to the ASN.1 type {@code PrivateKeyInfo}.
|
||||
* The {@code PrivateKeyInfo} syntax is defined in the PKCS#8 standard
|
||||
* encoded according to the ASN.1 type {@code OneAsymmetricKey}.
|
||||
* The {@code OneAsymmetricKey} syntax is defined in the PKCS#8 standard
|
||||
* as follows:
|
||||
*
|
||||
* <pre>
|
||||
* PrivateKeyInfo ::= SEQUENCE {
|
||||
* OneAsymmetricKey ::= SEQUENCE {
|
||||
* version Version,
|
||||
* privateKeyAlgorithm PrivateKeyAlgorithmIdentifier,
|
||||
* privateKey PrivateKey,
|
||||
* attributes [0] IMPLICIT Attributes OPTIONAL }
|
||||
* attributes [0] Attributes OPTIONAL,
|
||||
* ...,
|
||||
* [[2: publicKey [1] PublicKey OPTIONAL ]],
|
||||
* ...
|
||||
* }
|
||||
*
|
||||
* Version ::= INTEGER
|
||||
* PrivateKeyInfo ::= OneAsymmetricKey
|
||||
*
|
||||
* Version ::= INTEGER { v1(0), v2(1) }
|
||||
*
|
||||
* PrivateKeyAlgorithmIdentifier ::= AlgorithmIdentifier
|
||||
*
|
||||
* PrivateKey ::= OCTET STRING
|
||||
*
|
||||
* PublicKey ::= BIT STRING
|
||||
*
|
||||
* Attributes ::= SET OF Attribute
|
||||
* </pre>
|
||||
*
|
||||
@ -56,11 +66,14 @@ package java.security.spec;
|
||||
* @see EncodedKeySpec
|
||||
* @see X509EncodedKeySpec
|
||||
*
|
||||
* @spec https://www.rfc-editor.org/info/rfc5958
|
||||
* RFC 5958: Asymmetric Key Packages
|
||||
*
|
||||
* @since 1.2
|
||||
*/
|
||||
|
||||
public class PKCS8EncodedKeySpec extends EncodedKeySpec {
|
||||
|
||||
public non-sealed class PKCS8EncodedKeySpec extends EncodedKeySpec implements
|
||||
DEREncodable {
|
||||
/**
|
||||
* Creates a new {@code PKCS8EncodedKeySpec} with the given encoded key.
|
||||
*
|
||||
|
@ -25,6 +25,8 @@
|
||||
|
||||
package java.security.spec;
|
||||
|
||||
import java.security.DEREncodable;
|
||||
|
||||
/**
|
||||
* This class represents the ASN.1 encoding of a public key,
|
||||
* encoded according to the ASN.1 type {@code SubjectPublicKeyInfo}.
|
||||
@ -49,8 +51,8 @@ package java.security.spec;
|
||||
* @since 1.2
|
||||
*/
|
||||
|
||||
public class X509EncodedKeySpec extends EncodedKeySpec {
|
||||
|
||||
public non-sealed class X509EncodedKeySpec extends EncodedKeySpec implements
|
||||
DEREncodable {
|
||||
/**
|
||||
* Creates a new {@code X509EncodedKeySpec} with the given encoded key.
|
||||
*
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2001, 2024, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2001, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -25,13 +25,18 @@
|
||||
|
||||
package javax.crypto;
|
||||
|
||||
import java.io.*;
|
||||
import jdk.internal.javac.PreviewFeature;
|
||||
|
||||
import sun.security.jca.JCAUtil;
|
||||
import sun.security.pkcs.PKCS8Key;
|
||||
import sun.security.util.*;
|
||||
import sun.security.x509.AlgorithmId;
|
||||
|
||||
import javax.crypto.spec.PBEKeySpec;
|
||||
import java.io.IOException;
|
||||
import java.security.*;
|
||||
import java.security.spec.*;
|
||||
import sun.security.x509.AlgorithmId;
|
||||
import sun.security.util.DerValue;
|
||||
import sun.security.util.DerInputStream;
|
||||
import sun.security.util.DerOutputStream;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* This class implements the {@code EncryptedPrivateKeyInfo} type
|
||||
@ -55,14 +60,14 @@ import sun.security.util.DerOutputStream;
|
||||
* @since 1.4
|
||||
*/
|
||||
|
||||
public class EncryptedPrivateKeyInfo {
|
||||
public non-sealed class EncryptedPrivateKeyInfo implements DEREncodable {
|
||||
|
||||
// The "encryptionAlgorithm" is stored in either the algid or
|
||||
// the params field. Precisely, if this object is created by
|
||||
// {@link #EncryptedPrivateKeyInfo(AlgorithmParameters, byte[])}
|
||||
// with an uninitialized AlgorithmParameters, the AlgorithmParameters
|
||||
// object is stored in the params field and algid is set to null.
|
||||
// In all other cases, algid is non null and params is null.
|
||||
// In all other cases, algid is non-null and params is null.
|
||||
private final AlgorithmId algid;
|
||||
private final AlgorithmParameters params;
|
||||
|
||||
@ -73,19 +78,15 @@ public class EncryptedPrivateKeyInfo {
|
||||
private final byte[] encoded;
|
||||
|
||||
/**
|
||||
* Constructs (i.e., parses) an {@code EncryptedPrivateKeyInfo} from
|
||||
* its ASN.1 encoding.
|
||||
* Constructs an {@code EncryptedPrivateKeyInfo} from a given encrypted
|
||||
* PKCS#8 ASN.1 encoding.
|
||||
* @param encoded the ASN.1 encoding of this object. The contents of
|
||||
* the array are copied to protect against subsequent modification.
|
||||
* @exception NullPointerException if the {@code encoded} is
|
||||
* {@code null}.
|
||||
* @exception IOException if error occurs when parsing the ASN.1 encoding.
|
||||
* @throws NullPointerException if {@code encoded} is {@code null}.
|
||||
* @throws IOException if error occurs when parsing the ASN.1 encoding.
|
||||
*/
|
||||
public EncryptedPrivateKeyInfo(byte[] encoded) throws IOException {
|
||||
if (encoded == null) {
|
||||
throw new NullPointerException("the encoded parameter " +
|
||||
"must be non-null");
|
||||
}
|
||||
Objects.requireNonNull(encoded);
|
||||
|
||||
this.encoded = encoded.clone();
|
||||
DerValue val = DerValue.wrap(this.encoded);
|
||||
@ -201,7 +202,7 @@ public class EncryptedPrivateKeyInfo {
|
||||
tmp = null;
|
||||
}
|
||||
|
||||
// one and only one is non null
|
||||
// one and only one is non-null
|
||||
this.algid = tmp;
|
||||
this.params = this.algid != null ? null : algParams;
|
||||
|
||||
@ -219,6 +220,17 @@ public class EncryptedPrivateKeyInfo {
|
||||
this.encoded = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an EncryptedPrivateKeyInfo object from the given components
|
||||
*/
|
||||
private EncryptedPrivateKeyInfo(byte[] encoded, byte[] eData,
|
||||
AlgorithmId id, AlgorithmParameters p) {
|
||||
this.encoded = encoded;
|
||||
encryptedData = eData;
|
||||
algid = id;
|
||||
params = p;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the encryption algorithm.
|
||||
* <p>Note: Standard name is returned instead of the specified one
|
||||
@ -308,6 +320,242 @@ public class EncryptedPrivateKeyInfo {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and encrypts an {@code EncryptedPrivateKeyInfo} from a given
|
||||
* {@code PrivateKey}. A valid password-based encryption (PBE) algorithm
|
||||
* and password must be specified.
|
||||
*
|
||||
* <p> The PBE algorithm string format details can be found in the
|
||||
* <a href="{@docRoot}/../specs/security/standard-names.html#cipher-algorithms">
|
||||
* Cipher section</a> of the Java Security Standard Algorithm Names
|
||||
* Specification.
|
||||
*
|
||||
* @param key the {@code PrivateKey} to be encrypted
|
||||
* @param password the password used in the PBE encryption. This array
|
||||
* will be cloned before being used.
|
||||
* @param algorithm the PBE encryption algorithm. The default algorithm
|
||||
* will be used if {@code null}. However, {@code null} is
|
||||
* not allowed when {@code params} is non-null.
|
||||
* @param params the {@code AlgorithmParameterSpec} to be used with
|
||||
* encryption. The provider default will be used if
|
||||
* {@code null}.
|
||||
* @param provider the {@code Provider} will be used for PBE
|
||||
* {@link SecretKeyFactory} generation and {@link Cipher}
|
||||
* encryption operations. The default provider list will be
|
||||
* used if {@code null}.
|
||||
* @return an {@code EncryptedPrivateKeyInfo}
|
||||
* @throws IllegalArgumentException on initialization errors based on the
|
||||
* arguments passed to the method
|
||||
* @throws RuntimeException on an encryption error
|
||||
* @throws NullPointerException if the key or password are {@code null}. If
|
||||
* {@code params} is non-null when {@code algorithm} is {@code null}.
|
||||
*
|
||||
* @implNote The {@code jdk.epkcs8.defaultAlgorithm} Security Property
|
||||
* defines the default encryption algorithm and the
|
||||
* {@code AlgorithmParameterSpec} are the provider's algorithm defaults.
|
||||
*
|
||||
* @since 25
|
||||
*/
|
||||
@PreviewFeature(feature = PreviewFeature.Feature.PEM_API)
|
||||
public static EncryptedPrivateKeyInfo encryptKey(PrivateKey key,
|
||||
char[] password, String algorithm, AlgorithmParameterSpec params,
|
||||
Provider provider) {
|
||||
|
||||
SecretKey skey;
|
||||
Objects.requireNonNull(key, "key cannot be null");
|
||||
Objects.requireNonNull(password, "password cannot be null.");
|
||||
PBEKeySpec keySpec = new PBEKeySpec(password);
|
||||
if (algorithm == null) {
|
||||
if (params != null) {
|
||||
throw new NullPointerException("algorithm must be specified" +
|
||||
" if params is non-null.");
|
||||
}
|
||||
algorithm = Pem.DEFAULT_ALGO;
|
||||
}
|
||||
|
||||
try {
|
||||
SecretKeyFactory factory;
|
||||
if (provider == null) {
|
||||
factory = SecretKeyFactory.getInstance(algorithm);
|
||||
} else {
|
||||
factory = SecretKeyFactory.getInstance(algorithm, provider);
|
||||
}
|
||||
skey = factory.generateSecret(keySpec);
|
||||
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
return encryptKeyImpl(key, algorithm, skey, params, provider, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and encrypts an {@code EncryptedPrivateKeyInfo} from a given
|
||||
* {@code PrivateKey} and password. Default algorithm and parameters are
|
||||
* used.
|
||||
*
|
||||
* @param key the {@code PrivateKey} to be encrypted
|
||||
* @param password the password used in the PBE encryption. This array
|
||||
* will be cloned before being used.
|
||||
* @return an {@code EncryptedPrivateKeyInfo}
|
||||
* @throws IllegalArgumentException on initialization errors based on the
|
||||
* arguments passed to the method
|
||||
* @throws RuntimeException on an encryption error
|
||||
* @throws NullPointerException when the {@code key} or {@code password}
|
||||
* is {@code null}
|
||||
*
|
||||
* @implNote The {@code jdk.epkcs8.defaultAlgorithm} Security Property
|
||||
* defines the default encryption algorithm and the
|
||||
* {@code AlgorithmParameterSpec} are the provider's algorithm defaults.
|
||||
*
|
||||
* @since 25
|
||||
*/
|
||||
@PreviewFeature(feature = PreviewFeature.Feature.PEM_API)
|
||||
public static EncryptedPrivateKeyInfo encryptKey(PrivateKey key,
|
||||
char[] password) {
|
||||
return encryptKey(key, password, Pem.DEFAULT_ALGO, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and encrypts an {@code EncryptedPrivateKeyInfo} from the given
|
||||
* {@link PrivateKey} using the {@code encKey} and given parameters.
|
||||
*
|
||||
* @param key the {@code PrivateKey} to be encrypted
|
||||
* @param encKey the password-based encryption (PBE) {@code Key} used to
|
||||
* encrypt {@code key}.
|
||||
* @param algorithm the PBE encryption algorithm. The default algorithm is
|
||||
* will be used if {@code null}; however, {@code null} is
|
||||
* not allowed when {@code params} is non-null.
|
||||
* @param params the {@code AlgorithmParameterSpec} to be used with
|
||||
* encryption. The provider list default will be used if
|
||||
* {@code null}.
|
||||
* @param random the {@code SecureRandom} instance used during
|
||||
* encryption. The default will be used if {@code null}.
|
||||
* @param provider the {@code Provider} is used for {@link Cipher}
|
||||
* encryption operation. The default provider list will be
|
||||
* used if {@code null}.
|
||||
* @return an {@code EncryptedPrivateKeyInfo}
|
||||
* @throws IllegalArgumentException on initialization errors based on the
|
||||
* arguments passed to the method
|
||||
* @throws RuntimeException on an encryption error
|
||||
* @throws NullPointerException if the {@code key} or {@code encKey} are
|
||||
* {@code null}. If {@code params} is non-null, {@code algorithm} cannot be
|
||||
* {@code null}.
|
||||
*
|
||||
* @implNote The {@code jdk.epkcs8.defaultAlgorithm} Security Property
|
||||
* defines the default encryption algorithm and the
|
||||
* {@code AlgorithmParameterSpec} are the provider's algorithm defaults.
|
||||
*
|
||||
* @since 25
|
||||
*/
|
||||
@PreviewFeature(feature = PreviewFeature.Feature.PEM_API)
|
||||
public static EncryptedPrivateKeyInfo encryptKey(PrivateKey key, Key encKey,
|
||||
String algorithm, AlgorithmParameterSpec params, Provider provider,
|
||||
SecureRandom random) {
|
||||
|
||||
Objects.requireNonNull(key);
|
||||
Objects.requireNonNull(encKey);
|
||||
if (algorithm == null) {
|
||||
if (params != null) {
|
||||
throw new NullPointerException("algorithm must be specified " +
|
||||
"if params is non-null.");
|
||||
}
|
||||
algorithm = Pem.DEFAULT_ALGO;
|
||||
}
|
||||
return encryptKeyImpl(key, algorithm, encKey, params, provider, random);
|
||||
}
|
||||
|
||||
private static EncryptedPrivateKeyInfo encryptKeyImpl(PrivateKey key,
|
||||
String algorithm, Key encryptKey, AlgorithmParameterSpec params,
|
||||
Provider provider, SecureRandom random) {
|
||||
AlgorithmId algId;
|
||||
byte[] encryptedData;
|
||||
Cipher c;
|
||||
DerOutputStream out;
|
||||
|
||||
if (random == null) {
|
||||
random = JCAUtil.getDefSecureRandom();
|
||||
}
|
||||
try {
|
||||
if (provider == null) {
|
||||
c = Cipher.getInstance(algorithm);
|
||||
} else {
|
||||
c = Cipher.getInstance(algorithm, provider);
|
||||
}
|
||||
c.init(Cipher.ENCRYPT_MODE, encryptKey, params, random);
|
||||
encryptedData = c.doFinal(key.getEncoded());
|
||||
algId = new AlgorithmId(Pem.getPBEID(algorithm), c.getParameters());
|
||||
out = new DerOutputStream();
|
||||
algId.encode(out);
|
||||
out.putOctetString(encryptedData);
|
||||
} catch (InvalidAlgorithmParameterException | NoSuchAlgorithmException |
|
||||
NoSuchPaddingException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
} catch (IllegalBlockSizeException | BadPaddingException |
|
||||
InvalidKeyException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return new EncryptedPrivateKeyInfo(
|
||||
DerValue.wrap(DerValue.tag_Sequence, out).toByteArray(),
|
||||
encryptedData, algId, c.getParameters());
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the enclosed {@code PrivateKey} object from the encrypted data
|
||||
* and return it.
|
||||
*
|
||||
* @param password the password used in the PBE encryption. This array
|
||||
* will be cloned before being used.
|
||||
* @return a {@code PrivateKey}
|
||||
* @throws GeneralSecurityException if an error occurs parsing or
|
||||
* decrypting the encrypted data, or producing the key object.
|
||||
* @throws NullPointerException if {@code password} is null
|
||||
*
|
||||
* @since 25
|
||||
*/
|
||||
@PreviewFeature(feature = PreviewFeature.Feature.PEM_API)
|
||||
public PrivateKey getKey(char[] password) throws GeneralSecurityException {
|
||||
SecretKeyFactory skf;
|
||||
PKCS8EncodedKeySpec p8KeySpec;
|
||||
Objects.requireNonNull(password, "password cannot be null");
|
||||
PBEKeySpec keySpec = new PBEKeySpec(password);
|
||||
skf = SecretKeyFactory.getInstance(getAlgName());
|
||||
p8KeySpec = getKeySpec(skf.generateSecret(keySpec));
|
||||
|
||||
return PKCS8Key.parseKey(p8KeySpec.getEncoded());
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the enclosed {@code PrivateKey} object from the encrypted data
|
||||
* and return it.
|
||||
*
|
||||
* @param decryptKey the decryption key and cannot be {@code null}
|
||||
* @param provider the {@code Provider} used for Cipher decryption and
|
||||
* {@code PrivateKey} generation. A {@code null} value will
|
||||
* use the default provider configuration.
|
||||
* @return a {@code PrivateKey}
|
||||
* @throws GeneralSecurityException if an error occurs parsing or
|
||||
* decrypting the encrypted data, or producing the key object.
|
||||
* @throws NullPointerException if {@code decryptKey} is null
|
||||
*
|
||||
* @since 25
|
||||
*/
|
||||
@PreviewFeature(feature = PreviewFeature.Feature.PEM_API)
|
||||
public PrivateKey getKey(Key decryptKey, Provider provider)
|
||||
throws GeneralSecurityException {
|
||||
Objects.requireNonNull(decryptKey,"decryptKey cannot be null.");
|
||||
PKCS8EncodedKeySpec p = getKeySpecImpl(decryptKey, provider);
|
||||
try {
|
||||
if (provider == null) {
|
||||
return KeyFactory.getInstance(
|
||||
KeyUtil.getAlgorithm(p.getEncoded())).
|
||||
generatePrivate(p);
|
||||
}
|
||||
return KeyFactory.getInstance(KeyUtil.getAlgorithm(p.getEncoded()),
|
||||
provider).generatePrivate(p);
|
||||
} catch (IOException e) {
|
||||
throw new GeneralSecurityException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the enclosed PKCS8EncodedKeySpec object from the
|
||||
* encrypted data and return it.
|
||||
@ -353,12 +601,8 @@ public class EncryptedPrivateKeyInfo {
|
||||
public PKCS8EncodedKeySpec getKeySpec(Key decryptKey,
|
||||
String providerName) throws NoSuchProviderException,
|
||||
NoSuchAlgorithmException, InvalidKeyException {
|
||||
if (decryptKey == null) {
|
||||
throw new NullPointerException("decryptKey is null");
|
||||
}
|
||||
if (providerName == null) {
|
||||
throw new NullPointerException("provider is null");
|
||||
}
|
||||
Objects.requireNonNull(decryptKey, "decryptKey is null");
|
||||
Objects.requireNonNull(providerName, "provider is null");
|
||||
Provider provider = Security.getProvider(providerName);
|
||||
if (provider == null) {
|
||||
throw new NoSuchProviderException("provider " +
|
||||
@ -387,12 +631,8 @@ public class EncryptedPrivateKeyInfo {
|
||||
public PKCS8EncodedKeySpec getKeySpec(Key decryptKey,
|
||||
Provider provider) throws NoSuchAlgorithmException,
|
||||
InvalidKeyException {
|
||||
if (decryptKey == null) {
|
||||
throw new NullPointerException("decryptKey is null");
|
||||
}
|
||||
if (provider == null) {
|
||||
throw new NullPointerException("provider is null");
|
||||
}
|
||||
Objects.requireNonNull(decryptKey, "decryptKey is null");
|
||||
Objects.requireNonNull(provider, "provider is null");
|
||||
return getKeySpecImpl(decryptKey, provider);
|
||||
}
|
||||
|
||||
@ -438,23 +678,9 @@ public class EncryptedPrivateKeyInfo {
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("fallthrough")
|
||||
private static PKCS8EncodedKeySpec pkcs8EncodingToSpec(byte[] encodedKey)
|
||||
throws IOException {
|
||||
DerInputStream in = new DerInputStream(encodedKey);
|
||||
DerValue[] values = in.getSequence(3);
|
||||
|
||||
switch (values.length) {
|
||||
case 4:
|
||||
checkTag(values[3], DerValue.TAG_CONTEXT, "attributes");
|
||||
/* fall through */
|
||||
case 3:
|
||||
checkTag(values[0], DerValue.tag_Integer, "version");
|
||||
String keyAlg = AlgorithmId.parse(values[1]).getName();
|
||||
checkTag(values[2], DerValue.tag_OctetString, "privateKey");
|
||||
return new PKCS8EncodedKeySpec(encodedKey, keyAlg);
|
||||
default:
|
||||
throw new IOException("invalid key encoding");
|
||||
}
|
||||
return new PKCS8EncodedKeySpec(encodedKey,
|
||||
KeyUtil.getAlgorithm(encodedKey));
|
||||
}
|
||||
}
|
||||
|
@ -78,6 +78,8 @@ public @interface PreviewFeature {
|
||||
KEY_DERIVATION, //remove when the boot JDK is JDK 25
|
||||
@JEP(number = 502, title = "Stable Values", status = "Preview")
|
||||
STABLE_VALUES,
|
||||
@JEP(number=470, title="PEM Encodings of Cryptographic Objects", status="Preview")
|
||||
PEM_API,
|
||||
LANGUAGE_MODEL,
|
||||
/**
|
||||
* A key for testing.
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2006, 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2006, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -25,6 +25,8 @@
|
||||
|
||||
package sun.security.ec;
|
||||
|
||||
import sun.security.pkcs.PKCS8Key;
|
||||
|
||||
import java.security.*;
|
||||
import java.security.interfaces.*;
|
||||
import java.security.spec.*;
|
||||
@ -84,8 +86,7 @@ public final class ECKeyFactory extends KeyFactorySpi {
|
||||
* To be used by future Java ECDSA and ECDH implementations.
|
||||
*/
|
||||
public static ECKey toECKey(Key key) throws InvalidKeyException {
|
||||
if (key instanceof ECKey) {
|
||||
ECKey ecKey = (ECKey)key;
|
||||
if (key instanceof ECKey ecKey) {
|
||||
checkKey(ecKey);
|
||||
return ecKey;
|
||||
} else {
|
||||
@ -147,7 +148,7 @@ public final class ECKeyFactory extends KeyFactorySpi {
|
||||
|
||||
// see JCA doc
|
||||
protected PublicKey engineGeneratePublic(KeySpec keySpec)
|
||||
throws InvalidKeySpecException {
|
||||
throws InvalidKeySpecException {
|
||||
try {
|
||||
return implGeneratePublic(keySpec);
|
||||
} catch (InvalidKeySpecException e) {
|
||||
@ -159,7 +160,7 @@ public final class ECKeyFactory extends KeyFactorySpi {
|
||||
|
||||
// see JCA doc
|
||||
protected PrivateKey engineGeneratePrivate(KeySpec keySpec)
|
||||
throws InvalidKeySpecException {
|
||||
throws InvalidKeySpecException {
|
||||
try {
|
||||
return implGeneratePrivate(keySpec);
|
||||
} catch (InvalidKeySpecException e) {
|
||||
@ -171,19 +172,13 @@ public final class ECKeyFactory extends KeyFactorySpi {
|
||||
|
||||
// internal implementation of translateKey() for public keys. See JCA doc
|
||||
private PublicKey implTranslatePublicKey(PublicKey key)
|
||||
throws InvalidKeyException {
|
||||
if (key instanceof ECPublicKey) {
|
||||
if (key instanceof ECPublicKeyImpl) {
|
||||
return key;
|
||||
}
|
||||
ECPublicKey ecKey = (ECPublicKey)key;
|
||||
return new ECPublicKeyImpl(
|
||||
ecKey.getW(),
|
||||
ecKey.getParams()
|
||||
);
|
||||
throws InvalidKeyException {
|
||||
if (key instanceof ECPublicKeyImpl) {
|
||||
return key;
|
||||
} else if (key instanceof ECPublicKey ecKey) {
|
||||
return new ECPublicKeyImpl(ecKey.getW(), ecKey.getParams());
|
||||
} else if ("X.509".equals(key.getFormat())) {
|
||||
byte[] encoded = key.getEncoded();
|
||||
return new ECPublicKeyImpl(encoded);
|
||||
return new ECPublicKeyImpl(key.getEncoded());
|
||||
} else {
|
||||
throw new InvalidKeyException("Public keys must be instance "
|
||||
+ "of ECPublicKey or have X.509 encoding");
|
||||
@ -192,16 +187,11 @@ public final class ECKeyFactory extends KeyFactorySpi {
|
||||
|
||||
// internal implementation of translateKey() for private keys. See JCA doc
|
||||
private PrivateKey implTranslatePrivateKey(PrivateKey key)
|
||||
throws InvalidKeyException {
|
||||
if (key instanceof ECPrivateKey) {
|
||||
if (key instanceof ECPrivateKeyImpl) {
|
||||
return key;
|
||||
}
|
||||
ECPrivateKey ecKey = (ECPrivateKey)key;
|
||||
return new ECPrivateKeyImpl(
|
||||
ecKey.getS(),
|
||||
ecKey.getParams()
|
||||
);
|
||||
throws InvalidKeyException {
|
||||
if (key instanceof ECPrivateKeyImpl) {
|
||||
return key;
|
||||
} else if (key instanceof ECPrivateKey ecKey) {
|
||||
return new ECPrivateKeyImpl(ecKey.getS(), ecKey.getParams());
|
||||
} else if ("PKCS#8".equals(key.getFormat())) {
|
||||
byte[] encoded = key.getEncoded();
|
||||
try {
|
||||
@ -209,52 +199,54 @@ public final class ECKeyFactory extends KeyFactorySpi {
|
||||
} finally {
|
||||
Arrays.fill(encoded, (byte)0);
|
||||
}
|
||||
} else {
|
||||
throw new InvalidKeyException("Private keys must be instance "
|
||||
+ "of ECPrivateKey or have PKCS#8 encoding");
|
||||
}
|
||||
|
||||
throw new InvalidKeyException("Private keys must be instance "
|
||||
+ "of ECPrivateKey or have PKCS#8 encoding");
|
||||
}
|
||||
|
||||
// internal implementation of generatePublic. See JCA doc
|
||||
private PublicKey implGeneratePublic(KeySpec keySpec)
|
||||
throws GeneralSecurityException {
|
||||
if (keySpec instanceof X509EncodedKeySpec) {
|
||||
X509EncodedKeySpec x509Spec = (X509EncodedKeySpec)keySpec;
|
||||
return new ECPublicKeyImpl(x509Spec.getEncoded());
|
||||
} else if (keySpec instanceof ECPublicKeySpec) {
|
||||
ECPublicKeySpec ecSpec = (ECPublicKeySpec)keySpec;
|
||||
return new ECPublicKeyImpl(
|
||||
ecSpec.getW(),
|
||||
ecSpec.getParams()
|
||||
);
|
||||
} else {
|
||||
throw new InvalidKeySpecException("Only ECPublicKeySpec "
|
||||
+ "and X509EncodedKeySpec supported for EC public keys");
|
||||
}
|
||||
throws GeneralSecurityException {
|
||||
return switch (keySpec) {
|
||||
case X509EncodedKeySpec x -> new ECPublicKeyImpl(x.getEncoded());
|
||||
case ECPublicKeySpec e ->
|
||||
new ECPublicKeyImpl(e.getW(), e.getParams());
|
||||
case PKCS8EncodedKeySpec p8 -> {
|
||||
PKCS8Key p8key = new ECPrivateKeyImpl(p8.getEncoded());
|
||||
if (!p8key.hasPublicKey()) {
|
||||
throw new InvalidKeySpecException("No public key found.");
|
||||
}
|
||||
yield new ECPublicKeyImpl(p8key.getPubKeyEncoded());
|
||||
}
|
||||
default ->
|
||||
throw new InvalidKeySpecException(keySpec.getClass().getName() +
|
||||
" not supported.");
|
||||
};
|
||||
}
|
||||
|
||||
// internal implementation of generatePrivate. See JCA doc
|
||||
private PrivateKey implGeneratePrivate(KeySpec keySpec)
|
||||
throws GeneralSecurityException {
|
||||
if (keySpec instanceof PKCS8EncodedKeySpec) {
|
||||
PKCS8EncodedKeySpec pkcsSpec = (PKCS8EncodedKeySpec)keySpec;
|
||||
byte[] encoded = pkcsSpec.getEncoded();
|
||||
try {
|
||||
return new ECPrivateKeyImpl(encoded);
|
||||
} finally {
|
||||
Arrays.fill(encoded, (byte) 0);
|
||||
throws GeneralSecurityException {
|
||||
return switch (keySpec) {
|
||||
case PKCS8EncodedKeySpec p8 -> {
|
||||
byte[] encoded = p8.getEncoded();
|
||||
try {
|
||||
yield new ECPrivateKeyImpl(encoded);
|
||||
} finally {
|
||||
Arrays.fill(encoded, (byte) 0);
|
||||
}
|
||||
}
|
||||
} else if (keySpec instanceof ECPrivateKeySpec) {
|
||||
ECPrivateKeySpec ecSpec = (ECPrivateKeySpec)keySpec;
|
||||
return new ECPrivateKeyImpl(ecSpec.getS(), ecSpec.getParams());
|
||||
} else {
|
||||
throw new InvalidKeySpecException("Only ECPrivateKeySpec "
|
||||
+ "and PKCS8EncodedKeySpec supported for EC private keys");
|
||||
}
|
||||
case ECPrivateKeySpec e ->
|
||||
new ECPrivateKeyImpl(e.getS(), e.getParams());
|
||||
default ->
|
||||
throw new InvalidKeySpecException(keySpec.getClass().getName() +
|
||||
" not supported.");
|
||||
};
|
||||
}
|
||||
|
||||
protected <T extends KeySpec> T engineGetKeySpec(Key key, Class<T> keySpec)
|
||||
throws InvalidKeySpecException {
|
||||
throws InvalidKeySpecException {
|
||||
try {
|
||||
// convert key to one of our keys
|
||||
// this also verifies that the key is a valid EC key and ensures
|
||||
@ -263,8 +255,7 @@ public final class ECKeyFactory extends KeyFactorySpi {
|
||||
} catch (InvalidKeyException e) {
|
||||
throw new InvalidKeySpecException(e);
|
||||
}
|
||||
if (key instanceof ECPublicKey) {
|
||||
ECPublicKey ecKey = (ECPublicKey)key;
|
||||
if (key instanceof ECPublicKey ecKey) {
|
||||
if (keySpec.isAssignableFrom(ECPublicKeySpec.class)) {
|
||||
return keySpec.cast(new ECPublicKeySpec(
|
||||
ecKey.getW(),
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2006, 2023, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2006, 2024, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -40,6 +40,7 @@ import sun.security.ec.point.MutablePoint;
|
||||
import sun.security.util.*;
|
||||
import sun.security.x509.AlgorithmId;
|
||||
import sun.security.pkcs.PKCS8Key;
|
||||
import sun.security.x509.X509Key;
|
||||
|
||||
/**
|
||||
* Key implementation for EC private keys.
|
||||
@ -73,6 +74,7 @@ public final class ECPrivateKeyImpl extends PKCS8Key implements ECPrivateKey {
|
||||
private byte[] arrayS; // private value as a little-endian array
|
||||
@SuppressWarnings("serial") // Type of field is not Serializable
|
||||
private ECParameterSpec params;
|
||||
private byte[] domainParams; //Currently unsupported
|
||||
|
||||
/**
|
||||
* Construct a key from its encoding. Called by the ECKeyFactory.
|
||||
@ -111,7 +113,7 @@ public final class ECPrivateKeyImpl extends PKCS8Key implements ECPrivateKey {
|
||||
out.putOctetString(privBytes);
|
||||
Arrays.fill(privBytes, (byte) 0);
|
||||
DerValue val = DerValue.wrap(DerValue.tag_Sequence, out);
|
||||
key = val.toByteArray();
|
||||
privKeyMaterial = val.toByteArray();
|
||||
val.clear();
|
||||
}
|
||||
|
||||
@ -133,7 +135,7 @@ public final class ECPrivateKeyImpl extends PKCS8Key implements ECPrivateKey {
|
||||
out.putOctetString(sOctets);
|
||||
Arrays.fill(sOctets, (byte) 0);
|
||||
DerValue val = DerValue.wrap(DerValue.tag_Sequence, out);
|
||||
key = val.toByteArray();
|
||||
privKeyMaterial = val.toByteArray();
|
||||
val.clear();
|
||||
}
|
||||
|
||||
@ -153,64 +155,78 @@ public final class ECPrivateKeyImpl extends PKCS8Key implements ECPrivateKey {
|
||||
return s;
|
||||
}
|
||||
|
||||
private byte[] getArrayS0() {
|
||||
// Return the internal arrayS byte[], if arrayS is null generate it.
|
||||
public byte[] getArrayS() {
|
||||
if (arrayS == null) {
|
||||
arrayS = ECUtil.sArray(getS(), params);
|
||||
}
|
||||
return arrayS;
|
||||
}
|
||||
|
||||
public byte[] getArrayS() {
|
||||
return getArrayS0().clone();
|
||||
}
|
||||
|
||||
// see JCA doc
|
||||
public ECParameterSpec getParams() {
|
||||
return params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the ASN.1 of the privateKey Octet
|
||||
*/
|
||||
private void parseKeyBits() throws InvalidKeyException {
|
||||
// Parse private key material from PKCS8Key.decode()
|
||||
try {
|
||||
DerInputStream in = new DerInputStream(key);
|
||||
DerInputStream in = new DerInputStream(privKeyMaterial);
|
||||
DerValue derValue = in.getDerValue();
|
||||
if (derValue.tag != DerValue.tag_Sequence) {
|
||||
throw new IOException("Not a SEQUENCE");
|
||||
}
|
||||
DerInputStream data = derValue.data;
|
||||
int version = data.getInteger();
|
||||
if (version != 1) {
|
||||
if (version != V2) {
|
||||
throw new IOException("Version must be 1");
|
||||
}
|
||||
byte[] privData = data.getOctetString();
|
||||
ArrayUtil.reverse(privData);
|
||||
arrayS = privData;
|
||||
while (data.available() != 0) {
|
||||
DerValue value = data.getDerValue();
|
||||
if (value.isContextSpecific((byte) 0)) {
|
||||
// ignore for now
|
||||
} else if (value.isContextSpecific((byte) 1)) {
|
||||
// ignore for now
|
||||
} else {
|
||||
throw new InvalidKeyException("Unexpected value: " + value);
|
||||
}
|
||||
}
|
||||
|
||||
// Validate parameters stored from PKCS8Key.decode()
|
||||
AlgorithmParameters algParams = this.algid.getParameters();
|
||||
if (algParams == null) {
|
||||
throw new InvalidKeyException("EC domain parameters must be "
|
||||
+ "encoded in the algorithm identifier");
|
||||
}
|
||||
params = algParams.getParameterSpec(ECParameterSpec.class);
|
||||
|
||||
if (data.available() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
DerValue value = data.getDerValue();
|
||||
if (value.isContextSpecific((byte) 0)) {
|
||||
domainParams = value.getDataBytes(); // Save DER sequence
|
||||
if (data.available() == 0) {
|
||||
return;
|
||||
}
|
||||
value = data.getDerValue();
|
||||
}
|
||||
|
||||
if (value.isContextSpecific((byte) 1)) {
|
||||
DerValue bits = value.withTag(DerValue.tag_BitString);
|
||||
pubKeyEncoded = new X509Key(algid,
|
||||
bits.data.getUnalignedBitString()).getEncoded();
|
||||
} else {
|
||||
throw new InvalidKeyException("Unexpected value: " + value);
|
||||
}
|
||||
|
||||
} catch (IOException | InvalidParameterSpecException e) {
|
||||
throw new InvalidKeyException("Invalid EC private key", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public PublicKey calculatePublicKey() {
|
||||
ECParameterSpec ecParams = getParams();
|
||||
ECOperations ops = ECOperations.forParameters(ecParams)
|
||||
.orElseThrow(ProviderException::new);
|
||||
MutablePoint pub = ops.multiply(ecParams.getGenerator(), getArrayS0());
|
||||
MutablePoint pub = ops.multiply(ecParams.getGenerator(), getArrayS());
|
||||
AffinePoint affPub = pub.asAffine();
|
||||
ECPoint w = new ECPoint(affPub.getX().asBigInteger(),
|
||||
affPub.getY().asBigInteger());
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2018, 2021, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -25,12 +25,9 @@
|
||||
|
||||
package sun.security.ec;
|
||||
|
||||
import java.security.KeyFactorySpi;
|
||||
import java.security.Key;
|
||||
import java.security.PublicKey;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.ProviderException;
|
||||
import sun.security.pkcs.PKCS8Key;
|
||||
|
||||
import java.security.*;
|
||||
import java.security.interfaces.XECKey;
|
||||
import java.security.interfaces.XECPrivateKey;
|
||||
import java.security.interfaces.XECPublicKey;
|
||||
@ -160,9 +157,19 @@ public class XDHKeyFactory extends KeyFactorySpi {
|
||||
InvalidKeySpecException::new, publicKeySpec.getParams());
|
||||
checkLockedParams(InvalidKeySpecException::new, params);
|
||||
return new XDHPublicKeyImpl(params, publicKeySpec.getU());
|
||||
} else if (keySpec instanceof PKCS8EncodedKeySpec p8) {
|
||||
PKCS8Key p8key = new XDHPrivateKeyImpl(p8.getEncoded());
|
||||
if (!p8key.hasPublicKey()) {
|
||||
throw new InvalidKeySpecException("No public key found.");
|
||||
}
|
||||
XDHPublicKeyImpl result =
|
||||
new XDHPublicKeyImpl(p8key.getPubKeyEncoded());
|
||||
checkLockedParams(InvalidKeySpecException::new,
|
||||
result.getParams());
|
||||
return result;
|
||||
} else {
|
||||
throw new InvalidKeySpecException(
|
||||
"Only X509EncodedKeySpec and XECPublicKeySpec are supported");
|
||||
throw new InvalidKeySpecException(keySpec.getClass().getName() +
|
||||
" not supported.");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2018, 2023, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -54,7 +54,7 @@ public final class XDHPrivateKeyImpl extends PKCS8Key implements XECPrivateKey {
|
||||
|
||||
DerValue val = new DerValue(DerValue.tag_OctetString, k);
|
||||
try {
|
||||
this.key = val.toByteArray();
|
||||
this.privKeyMaterial = val.toByteArray();
|
||||
} finally {
|
||||
val.clear();
|
||||
}
|
||||
@ -67,7 +67,7 @@ public final class XDHPrivateKeyImpl extends PKCS8Key implements XECPrivateKey {
|
||||
InvalidKeyException::new, algid);
|
||||
paramSpec = new NamedParameterSpec(params.getName());
|
||||
try {
|
||||
DerInputStream derStream = new DerInputStream(key);
|
||||
DerInputStream derStream = new DerInputStream(privKeyMaterial);
|
||||
k = derStream.getOctetString();
|
||||
} catch (IOException ex) {
|
||||
throw new InvalidKeyException(ex);
|
||||
@ -102,7 +102,6 @@ public final class XDHPrivateKeyImpl extends PKCS8Key implements XECPrivateKey {
|
||||
return Optional.of(getK());
|
||||
}
|
||||
|
||||
@Override
|
||||
public PublicKey calculatePublicKey() {
|
||||
XECParameters params = paramSpec.getName().equalsIgnoreCase("X25519")
|
||||
? XECParameters.X25519
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2020, 2021, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -25,12 +25,9 @@
|
||||
|
||||
package sun.security.ec.ed;
|
||||
|
||||
import java.security.KeyFactorySpi;
|
||||
import java.security.Key;
|
||||
import java.security.PublicKey;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.ProviderException;
|
||||
import sun.security.pkcs.PKCS8Key;
|
||||
|
||||
import java.security.*;
|
||||
import java.security.interfaces.*;
|
||||
import java.security.spec.*;
|
||||
import java.util.Arrays;
|
||||
@ -153,9 +150,15 @@ public class EdDSAKeyFactory extends KeyFactorySpi {
|
||||
InvalidKeySpecException::new, publicKeySpec.getParams());
|
||||
checkLockedParams(InvalidKeySpecException::new, params);
|
||||
return new EdDSAPublicKeyImpl(params, publicKeySpec.getPoint());
|
||||
} else if (keySpec instanceof PKCS8EncodedKeySpec p8) {
|
||||
PKCS8Key p8key = new EdDSAPrivateKeyImpl(p8.getEncoded());
|
||||
if (!p8key.hasPublicKey()) {
|
||||
throw new InvalidKeySpecException("No public key found.");
|
||||
}
|
||||
return new EdDSAPublicKeyImpl(p8key.getPubKeyEncoded());
|
||||
} else {
|
||||
throw new InvalidKeySpecException(
|
||||
"Only X509EncodedKeySpec and EdECPublicKeySpec are supported");
|
||||
throw new InvalidKeySpecException(keySpec.getClass().getName() +
|
||||
" not supported.");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2020, 2023, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -56,7 +56,7 @@ public final class EdDSAPrivateKeyImpl
|
||||
|
||||
DerValue val = new DerValue(DerValue.tag_OctetString, h);
|
||||
try {
|
||||
this.key = val.toByteArray();
|
||||
privKeyMaterial = val.toByteArray();
|
||||
} finally {
|
||||
val.clear();
|
||||
}
|
||||
@ -71,7 +71,7 @@ public final class EdDSAPrivateKeyImpl
|
||||
paramSpec = new NamedParameterSpec(params.getName());
|
||||
|
||||
try {
|
||||
DerInputStream derStream = new DerInputStream(key);
|
||||
DerInputStream derStream = new DerInputStream(privKeyMaterial);
|
||||
h = derStream.getOctetString();
|
||||
} catch (IOException ex) {
|
||||
throw new InvalidKeyException(ex);
|
||||
@ -81,8 +81,8 @@ public final class EdDSAPrivateKeyImpl
|
||||
|
||||
void checkLength(EdDSAParameters params) throws InvalidKeyException {
|
||||
|
||||
if (params.getKeyLength() != this.h.length) {
|
||||
throw new InvalidKeyException("key length is " + this.h.length +
|
||||
if (params.getKeyLength() != h.length) {
|
||||
throw new InvalidKeyException("key length is " + h.length +
|
||||
", key length must be " + params.getKeyLength());
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -75,7 +75,7 @@ public final class NamedPKCS8Key extends PKCS8Key {
|
||||
|
||||
DerValue val = new DerValue(DerValue.tag_OctetString, rawBytes);
|
||||
try {
|
||||
this.key = val.toByteArray();
|
||||
this.privKeyMaterial = val.toByteArray();
|
||||
} finally {
|
||||
val.clear();
|
||||
}
|
||||
@ -90,7 +90,7 @@ public final class NamedPKCS8Key extends PKCS8Key {
|
||||
if (algid.getEncodedParams() != null) {
|
||||
throw new InvalidKeyException("algorithm identifier has params");
|
||||
}
|
||||
rawBytes = new DerInputStream(key).getOctetString();
|
||||
rawBytes = new DerInputStream(privKeyMaterial).getOctetString();
|
||||
} catch (IOException e) {
|
||||
throw new InvalidKeyException("Cannot parse input", e);
|
||||
}
|
||||
@ -129,7 +129,7 @@ public final class NamedPKCS8Key extends PKCS8Key {
|
||||
@Override
|
||||
public void destroy() throws DestroyFailedException {
|
||||
Arrays.fill(rawBytes, (byte)0);
|
||||
Arrays.fill(key, (byte)0);
|
||||
Arrays.fill(privKeyMaterial, (byte)0);
|
||||
if (encodedKey != null) {
|
||||
Arrays.fill(encodedKey, (byte)0);
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 1996, 2023, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 1996, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -25,22 +25,18 @@
|
||||
|
||||
package sun.security.pkcs;
|
||||
|
||||
import java.io.*;
|
||||
import java.security.Key;
|
||||
import java.security.KeyRep;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import jdk.internal.access.SharedSecrets;
|
||||
import sun.security.util.*;
|
||||
import sun.security.x509.AlgorithmId;
|
||||
import sun.security.x509.X509Key;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.security.*;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.security.spec.PKCS8EncodedKeySpec;
|
||||
import java.util.Arrays;
|
||||
|
||||
import jdk.internal.access.SharedSecrets;
|
||||
import sun.security.x509.*;
|
||||
import sun.security.util.*;
|
||||
|
||||
/**
|
||||
* Holds a PKCS#8 key, for example a private key
|
||||
*
|
||||
@ -56,7 +52,7 @@ import sun.security.util.*;
|
||||
* ...
|
||||
* }
|
||||
*
|
||||
* We support this format but do not parse attributes and publicKey now.
|
||||
* We support this format but do not parse attributes.
|
||||
*/
|
||||
public class PKCS8Key implements PrivateKey, InternalPrivateKey {
|
||||
|
||||
@ -67,20 +63,29 @@ public class PKCS8Key implements PrivateKey, InternalPrivateKey {
|
||||
/* The algorithm information (name, parameters, etc). */
|
||||
protected AlgorithmId algid;
|
||||
|
||||
/* The key bytes, without the algorithm information */
|
||||
protected byte[] key;
|
||||
/* The private key OctetString for the algorithm subclasses to decode */
|
||||
protected byte[] privKeyMaterial;
|
||||
|
||||
/* The encoded for the key. Created on demand by encode(). */
|
||||
/* The pkcs8 encoding of this key(s). Created on demand. */
|
||||
protected byte[] encodedKey;
|
||||
|
||||
/* The encoded x509 public key for v2 */
|
||||
protected byte[] pubKeyEncoded = null;
|
||||
|
||||
/* ASN.1 Attributes */
|
||||
private byte[] attributes;
|
||||
|
||||
/* PKCS8 version of the PEM */
|
||||
private int version;
|
||||
|
||||
/* The version for this key */
|
||||
private static final int V1 = 0;
|
||||
private static final int V2 = 1;
|
||||
public static final int V1 = 0;
|
||||
public static final int V2 = 1;
|
||||
|
||||
/**
|
||||
* Default constructor. Constructors in subclasses that create a new key
|
||||
* from its components require this. These constructors must initialize
|
||||
* {@link #algid} and {@link #key}.
|
||||
* {@link #algid} and {@link #privKeyMaterial}.
|
||||
*/
|
||||
protected PKCS8Key() { }
|
||||
|
||||
@ -91,7 +96,7 @@ public class PKCS8Key implements PrivateKey, InternalPrivateKey {
|
||||
*
|
||||
* This method is also used by {@link #parseKey} to create a raw key.
|
||||
*/
|
||||
protected PKCS8Key(byte[] input) throws InvalidKeyException {
|
||||
public PKCS8Key(byte[] input) throws InvalidKeyException {
|
||||
try {
|
||||
decode(new DerValue(input));
|
||||
} catch (IOException e) {
|
||||
@ -99,39 +104,70 @@ public class PKCS8Key implements PrivateKey, InternalPrivateKey {
|
||||
}
|
||||
}
|
||||
|
||||
private PKCS8Key(byte[] privEncoding, byte[] pubEncoding)
|
||||
throws InvalidKeyException {
|
||||
this(privEncoding);
|
||||
pubKeyEncoded = pubEncoding;
|
||||
version = V2;
|
||||
}
|
||||
|
||||
public int getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method for decoding PKCS8 v1 and v2 formats. Decoded values are stored
|
||||
* in this class, key material remains in DER format for algorithm
|
||||
* subclasses to decode.
|
||||
*/
|
||||
private void decode(DerValue val) throws InvalidKeyException {
|
||||
try {
|
||||
if (val.tag != DerValue.tag_Sequence) {
|
||||
throw new InvalidKeyException("invalid key format");
|
||||
}
|
||||
|
||||
int version = val.data.getInteger();
|
||||
// Support check for V1, aka 0, and V2, aka 1.
|
||||
version = val.data.getInteger();
|
||||
if (version != V1 && version != V2) {
|
||||
throw new InvalidKeyException("unknown version: " + version);
|
||||
}
|
||||
algid = AlgorithmId.parse (val.data.getDerValue ());
|
||||
key = val.data.getOctetString();
|
||||
// Parse and store AlgorithmID
|
||||
algid = AlgorithmId.parse(val.data.getDerValue());
|
||||
|
||||
DerValue next;
|
||||
// Store key material for subclasses to parse
|
||||
privKeyMaterial = val.data.getOctetString();
|
||||
|
||||
// PKCS8 v1 typically ends here
|
||||
if (val.data.available() == 0) {
|
||||
return;
|
||||
}
|
||||
next = val.data.getDerValue();
|
||||
if (next.isContextSpecific((byte)0)) {
|
||||
|
||||
// OPTIONAL Context tag 0 for Attributes for PKCS8 v1 & v2
|
||||
// Uses 0xA0 context-specific/constructed or 0x80
|
||||
// context-specific/primitive.
|
||||
DerValue v = val.data.getDerValue();
|
||||
if (v.isContextSpecific((byte)0)) {
|
||||
attributes = v.getDataBytes(); // Save DER sequence
|
||||
if (val.data.available() == 0) {
|
||||
return;
|
||||
}
|
||||
next = val.data.getDerValue();
|
||||
v = val.data.getDerValue();
|
||||
}
|
||||
|
||||
if (next.isContextSpecific((byte)1)) {
|
||||
if (version == V1) {
|
||||
throw new InvalidKeyException("publicKey seen in v1");
|
||||
// OPTIONAL context tag 1 for Public Key for PKCS8 v2 only
|
||||
if (version == V2) {
|
||||
if (v.isContextSpecific((byte)1)) {
|
||||
DerValue bits = v.withTag(DerValue.tag_BitString);
|
||||
pubKeyEncoded = new X509Key(algid,
|
||||
bits.getUnalignedBitString()).getEncoded();
|
||||
} else {
|
||||
throw new InvalidKeyException("Invalid context tag");
|
||||
}
|
||||
if (val.data.available() == 0) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
throw new InvalidKeyException("Extra bytes");
|
||||
} catch (IOException e) {
|
||||
throw new InvalidKeyException("Unable to decode key", e);
|
||||
@ -154,17 +190,29 @@ public class PKCS8Key implements PrivateKey, InternalPrivateKey {
|
||||
* handling, that specific need can be accommodated.
|
||||
*
|
||||
* @param encoded the DER-encoded SubjectPublicKeyInfo value
|
||||
* @exception IOException on data format errors
|
||||
* @exception InvalidKeyException on data format errors
|
||||
*/
|
||||
public static PrivateKey parseKey(byte[] encoded) throws IOException {
|
||||
public static PrivateKey parseKey(byte[] encoded)
|
||||
throws InvalidKeyException {
|
||||
return parseKey(encoded, null);
|
||||
}
|
||||
|
||||
public static PrivateKey parseKey(byte[] encoded, Provider provider)
|
||||
throws InvalidKeyException {
|
||||
try {
|
||||
PKCS8Key rawKey = new PKCS8Key(encoded);
|
||||
byte[] internal = rawKey.getEncodedInternal();
|
||||
PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(internal);
|
||||
|
||||
PKCS8EncodedKeySpec pkcs8KeySpec =
|
||||
new PKCS8EncodedKeySpec(rawKey.generateEncoding());
|
||||
PrivateKey result = null;
|
||||
try {
|
||||
result = KeyFactory.getInstance(rawKey.algid.getName())
|
||||
if (provider == null) {
|
||||
result = KeyFactory.getInstance(rawKey.algid.getName())
|
||||
.generatePrivate(pkcs8KeySpec);
|
||||
} else {
|
||||
result = KeyFactory.getInstance(rawKey.algid.getName(),
|
||||
provider).generatePrivate(pkcs8KeySpec);
|
||||
}
|
||||
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
|
||||
// Ignore and return raw key
|
||||
result = rawKey;
|
||||
@ -176,8 +224,8 @@ public class PKCS8Key implements PrivateKey, InternalPrivateKey {
|
||||
.clearEncodedKeySpec(pkcs8KeySpec);
|
||||
}
|
||||
return result;
|
||||
} catch (InvalidKeyException e) {
|
||||
throw new IOException("corrupt private key", e);
|
||||
} catch (IOException e) {
|
||||
throw new InvalidKeyException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@ -188,10 +236,18 @@ public class PKCS8Key implements PrivateKey, InternalPrivateKey {
|
||||
return algid.getName();
|
||||
}
|
||||
|
||||
public byte[] getPubKeyEncoded() {
|
||||
return pubKeyEncoded;
|
||||
}
|
||||
|
||||
public boolean hasPublicKey() {
|
||||
return (pubKeyEncoded != null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the algorithm ID to be used with this key.
|
||||
*/
|
||||
public AlgorithmId getAlgorithmId () {
|
||||
public AlgorithmId getAlgorithmId () {
|
||||
return algid;
|
||||
}
|
||||
|
||||
@ -210,6 +266,25 @@ public class PKCS8Key implements PrivateKey, InternalPrivateKey {
|
||||
return "PKCS#8";
|
||||
}
|
||||
|
||||
/**
|
||||
* With a given encoded Public and Private key, generate and return a
|
||||
* PKCS8v2 DER-encoded byte[].
|
||||
*
|
||||
* @param pubKeyEncoded DER-encoded PublicKey
|
||||
* @param privKeyEncoded DER-encoded PrivateKey
|
||||
* @return DER-encoded byte array
|
||||
* @throws IOException thrown on encoding failure
|
||||
*/
|
||||
public static byte[] getEncoded(byte[] pubKeyEncoded, byte[] privKeyEncoded)
|
||||
throws IOException {
|
||||
try {
|
||||
return new PKCS8Key(privKeyEncoded, pubKeyEncoded).
|
||||
generateEncoding();
|
||||
} catch (InvalidKeyException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* DER-encodes this key as a byte array stored inside this object
|
||||
* and return it.
|
||||
@ -218,17 +293,53 @@ public class PKCS8Key implements PrivateKey, InternalPrivateKey {
|
||||
*/
|
||||
private synchronized byte[] getEncodedInternal() {
|
||||
if (encodedKey == null) {
|
||||
DerOutputStream tmp = new DerOutputStream();
|
||||
tmp.putInteger(V1);
|
||||
algid.encode(tmp);
|
||||
tmp.putOctetString(key);
|
||||
DerValue out = DerValue.wrap(DerValue.tag_Sequence, tmp);
|
||||
encodedKey = out.toByteArray();
|
||||
out.clear();
|
||||
try {
|
||||
encodedKey = generateEncoding();
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return encodedKey;
|
||||
}
|
||||
|
||||
private byte[] generateEncoding() throws IOException {
|
||||
DerOutputStream out = new DerOutputStream();
|
||||
out.putInteger(version);
|
||||
algid.encode(out);
|
||||
out.putOctetString(privKeyMaterial);
|
||||
|
||||
if (version == V2) {
|
||||
if (attributes != null) {
|
||||
out.writeImplicit(
|
||||
DerValue.createTag((byte) (DerValue.TAG_CONTEXT |
|
||||
DerValue.TAG_CONSTRUCT), false, (byte) 0),
|
||||
new DerOutputStream().putOctetString(attributes));
|
||||
|
||||
}
|
||||
|
||||
if (pubKeyEncoded != null) {
|
||||
X509Key x = new X509Key();
|
||||
try {
|
||||
x.decode(pubKeyEncoded);
|
||||
} catch (InvalidKeyException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
|
||||
// X509Key x = X509Key.parse(pubKeyEncoded);
|
||||
DerOutputStream pubOut = new DerOutputStream();
|
||||
pubOut.putUnalignedBitString(x.getKey());
|
||||
out.writeImplicit(
|
||||
DerValue.createTag(DerValue.TAG_CONTEXT, false,
|
||||
(byte) 1), pubOut);
|
||||
}
|
||||
}
|
||||
|
||||
DerValue val = DerValue.wrap(DerValue.tag_Sequence, out);
|
||||
encodedKey = val.toByteArray();
|
||||
val.clear();
|
||||
return encodedKey;
|
||||
}
|
||||
|
||||
@java.io.Serial
|
||||
protected Object writeReplace() throws java.io.ObjectStreamException {
|
||||
return new KeyRep(KeyRep.Type.PRIVATE,
|
||||
@ -298,6 +409,6 @@ public class PKCS8Key implements PrivateKey, InternalPrivateKey {
|
||||
if (encodedKey != null) {
|
||||
Arrays.fill(encodedKey, (byte)0);
|
||||
}
|
||||
Arrays.fill(key, (byte)0);
|
||||
Arrays.fill(privKeyMaterial, (byte)0);
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 1996, 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 1996, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -70,7 +70,7 @@ public final class DSAPrivateKey extends PKCS8Key
|
||||
|
||||
byte[] xbytes = x.toByteArray();
|
||||
DerValue val = new DerValue(DerValue.tag_Integer, xbytes);
|
||||
key = val.toByteArray();
|
||||
privKeyMaterial = val.toByteArray();
|
||||
val.clear();
|
||||
Arrays.fill(xbytes, (byte)0);
|
||||
}
|
||||
@ -81,7 +81,7 @@ public final class DSAPrivateKey extends PKCS8Key
|
||||
public DSAPrivateKey(byte[] encoded) throws InvalidKeyException {
|
||||
super(encoded);
|
||||
try {
|
||||
DerInputStream in = new DerInputStream(key);
|
||||
DerInputStream in = new DerInputStream(privKeyMaterial);
|
||||
x = in.getBigInteger();
|
||||
} catch (IOException e) {
|
||||
throw new InvalidKeyException(e.getMessage(), e);
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 1997, 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -25,7 +25,6 @@
|
||||
|
||||
package sun.security.provider;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.*;
|
||||
import java.util.Arrays;
|
||||
@ -300,8 +299,8 @@ final class KeyProtector {
|
||||
// which in turn parses the key material.
|
||||
try {
|
||||
return PKCS8Key.parseKey(plainKey);
|
||||
} catch (IOException ioe) {
|
||||
throw new UnrecoverableKeyException(ioe.getMessage());
|
||||
} catch (InvalidKeyException e) {
|
||||
throw new UnrecoverableKeyException(e.getMessage());
|
||||
} finally {
|
||||
Arrays.fill(plainKey, (byte)0);
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 1998, 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 1998, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -27,6 +27,7 @@ package sun.security.provider;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
import java.security.PEMRecord;
|
||||
import java.security.cert.*;
|
||||
import java.util.*;
|
||||
|
||||
@ -36,6 +37,7 @@ import sun.security.provider.certpath.X509CertPath;
|
||||
import sun.security.provider.certpath.X509CertificatePair;
|
||||
import sun.security.util.Cache;
|
||||
import sun.security.util.DerValue;
|
||||
import sun.security.util.Pem;
|
||||
import sun.security.x509.X509CRLImpl;
|
||||
import sun.security.x509.X509CertImpl;
|
||||
|
||||
@ -556,118 +558,20 @@ public class X509Factory extends CertificateFactorySpi {
|
||||
readBERInternal(is, bout, c);
|
||||
return bout.toByteArray();
|
||||
} else {
|
||||
// Read BASE64 encoded data, might skip info at the beginning
|
||||
ByteArrayOutputStream data = new ByteArrayOutputStream();
|
||||
|
||||
// Step 1: Read until header is found
|
||||
int hyphen = (c=='-') ? 1: 0; // count of consequent hyphens
|
||||
int last = (c=='-') ? -1: c; // the char before hyphen
|
||||
while (true) {
|
||||
int next = is.read();
|
||||
if (next == -1) {
|
||||
// We accept useless data after the last block,
|
||||
// say, empty lines.
|
||||
try {
|
||||
PEMRecord rec;
|
||||
try {
|
||||
rec = Pem.readPEM(is, (c == '-' ? true : false));
|
||||
} catch (EOFException e) {
|
||||
return null;
|
||||
}
|
||||
if (next == '-') {
|
||||
hyphen++;
|
||||
} else {
|
||||
hyphen = 0;
|
||||
last = next;
|
||||
}
|
||||
if (hyphen == 5 && (last == -1 || last == '\r' || last == '\n')) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Step 2: Read the rest of header, determine the line end
|
||||
int end;
|
||||
StringBuilder header = new StringBuilder("-----");
|
||||
while (true) {
|
||||
int next = is.read();
|
||||
if (next == -1) {
|
||||
throw new IOException("Incomplete data");
|
||||
}
|
||||
if (next == '\n') {
|
||||
end = '\n';
|
||||
break;
|
||||
}
|
||||
if (next == '\r') {
|
||||
next = is.read();
|
||||
if (next == -1) {
|
||||
throw new IOException("Incomplete data");
|
||||
}
|
||||
if (next == '\n') {
|
||||
end = '\n';
|
||||
} else {
|
||||
end = '\r';
|
||||
// Skip all white space chars
|
||||
if (next != 9 && next != 10 && next != 13 && next != 32) {
|
||||
data.write(next);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
header.append((char)next);
|
||||
}
|
||||
|
||||
// Step 3: Read the data
|
||||
while (true) {
|
||||
int next = is.read();
|
||||
if (next == -1) {
|
||||
throw new IOException("Incomplete data");
|
||||
}
|
||||
if (next != '-') {
|
||||
// Skip all white space chars
|
||||
if (next != 9 && next != 10 && next != 13 && next != 32) {
|
||||
data.write(next);
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Step 4: Consume the footer
|
||||
StringBuilder footer = new StringBuilder("-");
|
||||
while (true) {
|
||||
int next = is.read();
|
||||
// Add next == '\n' for maximum safety, in case endline
|
||||
// is not consistent.
|
||||
if (next == -1 || next == end || next == '\n') {
|
||||
break;
|
||||
}
|
||||
if (next != '\r') footer.append((char)next);
|
||||
}
|
||||
|
||||
checkHeaderFooter(header.toString().stripTrailing(),
|
||||
footer.toString().stripTrailing());
|
||||
|
||||
try {
|
||||
return Base64.getDecoder().decode(data.toByteArray());
|
||||
return Base64.getDecoder().decode(rec.pem());
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void checkHeaderFooter(String header,
|
||||
String footer) throws IOException {
|
||||
if (header.length() < 16 || !header.startsWith("-----BEGIN ") ||
|
||||
!header.endsWith("-----")) {
|
||||
throw new IOException("Illegal header: " + header);
|
||||
}
|
||||
if (footer.length() < 14 || !footer.startsWith("-----END ") ||
|
||||
!footer.endsWith("-----")) {
|
||||
throw new IOException("Illegal footer: " + footer);
|
||||
}
|
||||
String headerType = header.substring(11, header.length()-5);
|
||||
String footerType = footer.substring(9, footer.length()-5);
|
||||
if (!headerType.equals(footerType)) {
|
||||
throw new IOException("Header and footer do not match: " +
|
||||
header + " " + footer);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read one BER data block. This method is aware of indefinite-length BER
|
||||
* encoding and will read all the subsections in a recursive way
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2003, 2024, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2003, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -32,6 +32,7 @@ import java.security.interfaces.*;
|
||||
import java.security.spec.*;
|
||||
import java.util.Arrays;
|
||||
|
||||
import sun.security.pkcs.PKCS8Key;
|
||||
import sun.security.rsa.RSAUtil.KeyType;
|
||||
|
||||
/**
|
||||
@ -332,9 +333,15 @@ public class RSAKeyFactory extends KeyFactorySpi {
|
||||
} catch (ProviderException e) {
|
||||
throw new InvalidKeySpecException(e);
|
||||
}
|
||||
} else if (keySpec instanceof PKCS8EncodedKeySpec p8) {
|
||||
PKCS8Key p8key = new PKCS8Key(p8.getEncoded());
|
||||
if (!p8key.hasPublicKey()) {
|
||||
throw new InvalidKeySpecException("No public key found.");
|
||||
}
|
||||
return RSAPublicKeyImpl.newKey(type, "X.509",
|
||||
p8key.getPubKeyEncoded());
|
||||
} else {
|
||||
throw new InvalidKeySpecException("Only RSAPublicKeySpec "
|
||||
+ "and X509EncodedKeySpec supported for RSA public keys");
|
||||
throw new InvalidKeySpecException(keySpec.getClass().getName() + " not supported.");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2003, 2024, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2003, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -69,6 +69,7 @@ public final class RSAPrivateCrtKeyImpl
|
||||
private BigInteger qe; // prime exponent q
|
||||
private BigInteger coeff; // CRT coefficient
|
||||
|
||||
// RSA or RSA-PSS KeyType
|
||||
private final transient KeyType type;
|
||||
|
||||
// Optional parameters associated with this RSA key
|
||||
@ -101,7 +102,7 @@ public final class RSAPrivateCrtKeyImpl
|
||||
}
|
||||
case "PKCS#1":
|
||||
try {
|
||||
BigInteger[] comps = parseASN1(encoded);
|
||||
BigInteger[] comps = parsePKCS1(encoded);
|
||||
if ((comps[1].signum() == 0) || (comps[3].signum() == 0) ||
|
||||
(comps[4].signum() == 0) || (comps[5].signum() == 0) ||
|
||||
(comps[6].signum() == 0) || (comps[7].signum() == 0)) {
|
||||
@ -237,7 +238,7 @@ public final class RSAPrivateCrtKeyImpl
|
||||
Arrays.fill(nbytes[6], (byte) 0);
|
||||
Arrays.fill(nbytes[7], (byte) 0);
|
||||
DerValue val = DerValue.wrap(DerValue.tag_Sequence, out);
|
||||
key = val.toByteArray();
|
||||
privKeyMaterial = val.toByteArray();
|
||||
val.clear();
|
||||
}
|
||||
|
||||
@ -304,7 +305,7 @@ public final class RSAPrivateCrtKeyImpl
|
||||
// utility method for parsing DER encoding of RSA private keys in PKCS#1
|
||||
// format as defined in RFC 8017 Appendix A.1.2, i.e. SEQ of version, n,
|
||||
// e, d, p, q, pe, qe, and coeff, and return the parsed components.
|
||||
private static BigInteger[] parseASN1(byte[] raw) throws IOException {
|
||||
private static BigInteger[] parsePKCS1(byte[] raw) throws IOException {
|
||||
DerValue derValue = new DerValue(raw);
|
||||
try {
|
||||
if (derValue.tag != DerValue.tag_Sequence) {
|
||||
@ -337,7 +338,7 @@ public final class RSAPrivateCrtKeyImpl
|
||||
|
||||
private void parseKeyBits() throws InvalidKeyException {
|
||||
try {
|
||||
BigInteger[] comps = parseASN1(key);
|
||||
BigInteger[] comps = parsePKCS1(privKeyMaterial);
|
||||
n = comps[0];
|
||||
e = comps[1];
|
||||
d = comps[2];
|
||||
@ -351,6 +352,30 @@ public final class RSAPrivateCrtKeyImpl
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* With a given PKCS#1/slleay/OpenSSL old default RSA binary encoding,
|
||||
* decode and return the proper RSA encoded KeySpec
|
||||
* @param encoded RSA binary encoding
|
||||
* @return KeySpec
|
||||
* @throws InvalidKeyException on decoding failure
|
||||
*/
|
||||
public static KeySpec getKeySpec(byte[] encoded) throws
|
||||
InvalidKeyException {
|
||||
try {
|
||||
BigInteger[] comps = parsePKCS1(encoded);
|
||||
if ((comps[1].signum() == 0) || (comps[3].signum() == 0) ||
|
||||
(comps[4].signum() == 0) || (comps[5].signum() == 0) ||
|
||||
(comps[6].signum() == 0) || (comps[7].signum() == 0)) {
|
||||
return new RSAPrivateKeySpec(comps[0], comps[2]);
|
||||
} else {
|
||||
return new RSAPrivateCrtKeySpec(comps[0],
|
||||
comps[1], comps[2], comps[3], comps[4], comps[5],
|
||||
comps[6], comps[7]);
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
throw new InvalidKeyException("Invalid PKCS#1 encoding", ioe);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Restores the state of this object from the stream.
|
||||
* <p>
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2003, 2024, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2003, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -110,7 +110,7 @@ public final class RSAPrivateKeyImpl extends PKCS8Key implements RSAPrivateKey {
|
||||
out.putInteger(0);
|
||||
out.putInteger(0);
|
||||
DerValue val = DerValue.wrap(DerValue.tag_Sequence, out);
|
||||
key = val.toByteArray();
|
||||
privKeyMaterial = val.toByteArray();
|
||||
val.clear();
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2003, 2023, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2003, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -82,7 +82,7 @@ public final class RSAPublicKeyImpl extends X509Key implements RSAPublicKey {
|
||||
break;
|
||||
case "PKCS#1":
|
||||
try {
|
||||
BigInteger[] comps = parseASN1(encoded);
|
||||
BigInteger[] comps = parsePKCS1(encoded);
|
||||
key = new RSAPublicKeyImpl(type, null, comps[0], comps[1]);
|
||||
} catch (IOException ioe) {
|
||||
throw new InvalidKeyException("Invalid PKCS#1 encoding", ioe);
|
||||
@ -199,7 +199,7 @@ public final class RSAPublicKeyImpl extends X509Key implements RSAPublicKey {
|
||||
|
||||
// utility method for parsing DER encoding of RSA public keys in PKCS#1
|
||||
// format as defined in RFC 8017 Appendix A.1.1, i.e. SEQ of n and e.
|
||||
private static BigInteger[] parseASN1(byte[] raw) throws IOException {
|
||||
private static BigInteger[] parsePKCS1(byte[] raw) throws IOException {
|
||||
DerValue derValue = new DerValue(raw);
|
||||
if (derValue.tag != DerValue.tag_Sequence) {
|
||||
throw new IOException("Not a SEQUENCE");
|
||||
@ -218,7 +218,7 @@ public final class RSAPublicKeyImpl extends X509Key implements RSAPublicKey {
|
||||
*/
|
||||
protected void parseKeyBits() throws InvalidKeyException {
|
||||
try {
|
||||
BigInteger[] comps = parseASN1(getKey().toByteArray());
|
||||
BigInteger[] comps = parsePKCS1(getKey().toByteArray());
|
||||
n = comps[0];
|
||||
e = comps[1];
|
||||
} catch (IOException e) {
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 1996, 2024, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 1996, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -67,6 +67,7 @@ public class DerValue {
|
||||
|
||||
/** The tag class types */
|
||||
public static final byte TAG_UNIVERSAL = (byte)0x000;
|
||||
public static final byte TAG_CONSTRUCT = (byte)0x020;
|
||||
public static final byte TAG_APPLICATION = (byte)0x040;
|
||||
public static final byte TAG_CONTEXT = (byte)0x080;
|
||||
public static final byte TAG_PRIVATE = (byte)0x0c0;
|
||||
|
@ -41,6 +41,7 @@ import javax.security.auth.DestroyFailedException;
|
||||
import jdk.internal.access.SharedSecrets;
|
||||
|
||||
import sun.security.jca.JCAUtil;
|
||||
import sun.security.x509.AlgorithmId;
|
||||
|
||||
/**
|
||||
* A utility class to get key length, validate keys, etc.
|
||||
@ -478,5 +479,72 @@ public final class KeyUtil {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* With a given DER encoded bytes, read through and return the AlgorithmID
|
||||
* stored if it can be found. If none is found or there is an IOException,
|
||||
* null is returned.
|
||||
*
|
||||
* @param encoded DER encoded bytes
|
||||
* @return AlgorithmID stored in the DER encoded bytes or null.
|
||||
*/
|
||||
public static String getAlgorithm(byte[] encoded) throws IOException {
|
||||
try {
|
||||
return getAlgorithmId(encoded).getName();
|
||||
} catch (IOException e) {
|
||||
throw new IOException("No recognized algorithm detected in " +
|
||||
"encoding", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* With a given DER encoded bytes, read through and return the AlgorithmID
|
||||
* stored if it can be found.
|
||||
*
|
||||
* @param encoded DER encoded bytes
|
||||
* @return AlgorithmID stored in the DER encoded bytes
|
||||
* @throws IOException if there was a DER or other parsing error
|
||||
*/
|
||||
public static AlgorithmId getAlgorithmId(byte[] encoded) throws IOException {
|
||||
DerInputStream is = new DerInputStream(encoded);
|
||||
DerValue value = is.getDerValue();
|
||||
if (value.tag != DerValue.tag_Sequence) {
|
||||
throw new IOException("Unknown DER Format: Value 1 not a Sequence");
|
||||
}
|
||||
|
||||
is = value.data;
|
||||
value = is.getDerValue();
|
||||
// This route is for: RSAPublic, Encrypted RSAPrivate, EC Public,
|
||||
// Encrypted EC Private,
|
||||
if (value.tag == DerValue.tag_Sequence) {
|
||||
return AlgorithmId.parse(value);
|
||||
} else if (value.tag == DerValue.tag_Integer) {
|
||||
// RSAPrivate, ECPrivate
|
||||
// current value is version, which can be ignored
|
||||
value = is.getDerValue();
|
||||
if (value.tag == DerValue.tag_OctetString) {
|
||||
value = is.getDerValue();
|
||||
if (value.tag == DerValue.tag_Sequence) {
|
||||
return AlgorithmId.parse(value);
|
||||
} else {
|
||||
// OpenSSL/X9.62 (0xA0)
|
||||
ObjectIdentifier oid = value.data.getOID();
|
||||
AlgorithmId algo = new AlgorithmId(oid, (AlgorithmParameters) null);
|
||||
if (CurveDB.lookup(algo.getName()) != null) {
|
||||
return new AlgorithmId(AlgorithmId.EC_oid);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} else if (value.tag == DerValue.tag_Sequence) {
|
||||
// Public Key
|
||||
return AlgorithmId.parse(value);
|
||||
}
|
||||
|
||||
}
|
||||
throw new IOException("No algorithm detected");
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2015, 2023, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -25,14 +25,56 @@
|
||||
|
||||
package sun.security.util;
|
||||
|
||||
import java.io.IOException;
|
||||
import sun.security.x509.AlgorithmId;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.PEMRecord;
|
||||
import java.security.Security;
|
||||
import java.util.Arrays;
|
||||
import java.util.Base64;
|
||||
import java.util.HexFormat;
|
||||
import java.util.Objects;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* A utility class for PEM format encoding.
|
||||
*/
|
||||
public class Pem {
|
||||
private static final char WS = 0x20; // Whitespace
|
||||
private static final byte[] CRLF = new byte[] {'\r', '\n'};
|
||||
|
||||
// Default algorithm from jdk.epkcs8.defaultAlgorithm in java.security
|
||||
public static final String DEFAULT_ALGO;
|
||||
|
||||
// Pattern matching for EKPI operations
|
||||
private static final Pattern pbePattern;
|
||||
|
||||
// Lazy initialized PBES2 OID value
|
||||
private static ObjectIdentifier PBES2OID;
|
||||
|
||||
// Lazy initialized singleton encoder.
|
||||
private static Base64.Encoder b64Encoder;
|
||||
|
||||
static {
|
||||
String algo = Security.getProperty("jdk.epkcs8.defaultAlgorithm");
|
||||
DEFAULT_ALGO = (algo == null || algo.isBlank()) ?
|
||||
"PBEWithHmacSHA256AndAES_128" : algo;
|
||||
pbePattern = Pattern.compile("^PBEWith.*And.*",
|
||||
Pattern.CASE_INSENSITIVE);
|
||||
}
|
||||
|
||||
public static final String CERTIFICATE = "CERTIFICATE";
|
||||
public static final String X509_CRL = "X509 CRL";
|
||||
public static final String ENCRYPTED_PRIVATE_KEY = "ENCRYPTED PRIVATE KEY";
|
||||
public static final String PRIVATE_KEY = "PRIVATE KEY";
|
||||
public static final String RSA_PRIVATE_KEY = "RSA PRIVATE KEY";
|
||||
public static final String PUBLIC_KEY = "PUBLIC KEY";
|
||||
// old PEM types per RFC 7468
|
||||
public static final String X509_CERTIFICATE = "X509 CERTIFICATE";
|
||||
public static final String X_509_CERTIFICATE = "X.509 CERTIFICATE";
|
||||
public static final String CRL = "CRL";
|
||||
|
||||
/**
|
||||
* Decodes a PEM-encoded block.
|
||||
@ -40,15 +82,264 @@ public class Pem {
|
||||
* @param input the input string, according to RFC 1421, can only contain
|
||||
* characters in the base-64 alphabet and whitespaces.
|
||||
* @return the decoded bytes
|
||||
* @throws java.io.IOException if input is invalid
|
||||
*/
|
||||
public static byte[] decode(String input) throws IOException {
|
||||
byte[] src = input.replaceAll("\\s+", "")
|
||||
.getBytes(StandardCharsets.ISO_8859_1);
|
||||
try {
|
||||
public static byte[] decode(String input) {
|
||||
byte[] src = input.replaceAll("\\s+", "").
|
||||
getBytes(StandardCharsets.ISO_8859_1);
|
||||
return Base64.getDecoder().decode(src);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the OID for a given PBE algorithm. PBES1 has an OID for each
|
||||
* algorithm, while PBES2 has one OID for everything that complies with
|
||||
* the formatting. Therefore, if the algorithm is not PBES1, it will
|
||||
* return PBES2. Cipher will determine if this is a valid PBE algorithm.
|
||||
* PBES2 specifies AES as the cipher algorithm, but any block cipher could
|
||||
* be supported.
|
||||
*/
|
||||
public static ObjectIdentifier getPBEID(String algorithm) {
|
||||
|
||||
// Verify pattern matches PBE Standard Name spec
|
||||
if (!pbePattern.matcher(algorithm).matches()) {
|
||||
throw new IllegalArgumentException("Invalid algorithm format.");
|
||||
}
|
||||
|
||||
// Return the PBES1 OID if it matches
|
||||
try {
|
||||
return AlgorithmId.get(algorithm).getOID();
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
// fall-through
|
||||
}
|
||||
|
||||
// Lazy initialize
|
||||
if (PBES2OID == null) {
|
||||
try {
|
||||
// Set to the hardcoded OID in KnownOID.java
|
||||
PBES2OID = AlgorithmId.get("PBES2").getOID();
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
// Should never fail.
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}
|
||||
return PBES2OID;
|
||||
}
|
||||
|
||||
/*
|
||||
* RFC 7468 has some rules what generators should return given a historical
|
||||
* type name. This converts read in PEM to the RFC. Change the type to
|
||||
* be uniform is likely to help apps from not using all 3 certificate names.
|
||||
*/
|
||||
private static String typeConverter(String type) {
|
||||
return switch (type) {
|
||||
case Pem.X509_CERTIFICATE, Pem.X_509_CERTIFICATE -> Pem.CERTIFICATE;
|
||||
case Pem.CRL -> Pem.X509_CRL;
|
||||
default -> type;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the PEM text and return it in it's three components: header,
|
||||
* base64, and footer.
|
||||
*
|
||||
* The method will leave the stream after reading the end of line of the
|
||||
* footer or end of file
|
||||
* @param is an InputStream
|
||||
* @param shortHeader if true, the hyphen length is 4 because the first
|
||||
* hyphen is assumed to have been read. This is needed
|
||||
* for the CertificateFactory X509 implementation.
|
||||
* @return a new PEMRecord
|
||||
* @throws IOException on IO errors or PEM syntax errors that leave
|
||||
* the read position not at the end of a PEM block
|
||||
* @throws EOFException when at the unexpected end of the stream
|
||||
* @throws IllegalArgumentException when a PEM syntax error occurs,
|
||||
* but the read position in the stream is at the end of the block, so
|
||||
* future reads can be successful.
|
||||
*/
|
||||
public static PEMRecord readPEM(InputStream is, boolean shortHeader)
|
||||
throws IOException {
|
||||
Objects.requireNonNull(is);
|
||||
|
||||
int hyphen = (shortHeader ? 1 : 0);
|
||||
int eol = 0;
|
||||
|
||||
ByteArrayOutputStream os = new ByteArrayOutputStream(6);
|
||||
// Find starting hyphens
|
||||
do {
|
||||
int d = is.read();
|
||||
switch (d) {
|
||||
case '-' -> hyphen++;
|
||||
case -1 -> {
|
||||
if (os.size() == 0) {
|
||||
throw new EOFException("No data available");
|
||||
}
|
||||
throw new EOFException("No PEM data found");
|
||||
}
|
||||
default -> hyphen = 0;
|
||||
}
|
||||
os.write(d);
|
||||
} while (hyphen != 5);
|
||||
|
||||
StringBuilder sb = new StringBuilder(64);
|
||||
sb.append("-----");
|
||||
hyphen = 0;
|
||||
int c;
|
||||
|
||||
// Get header definition until first hyphen
|
||||
do {
|
||||
switch (c = is.read()) {
|
||||
case '-' -> hyphen++;
|
||||
case -1 -> throw new EOFException("Input ended prematurely");
|
||||
case '\n', '\r' -> throw new IOException("Incomplete header");
|
||||
default -> sb.append((char) c);
|
||||
}
|
||||
} while (hyphen == 0);
|
||||
|
||||
// Verify header ending with 5 hyphens.
|
||||
do {
|
||||
switch (is.read()) {
|
||||
case '-' -> hyphen++;
|
||||
default ->
|
||||
throw new IOException("Incomplete header");
|
||||
}
|
||||
} while (hyphen < 5);
|
||||
|
||||
sb.append("-----");
|
||||
String header = sb.toString();
|
||||
if (header.length() < 16 || !header.startsWith("-----BEGIN ") ||
|
||||
!header.endsWith("-----")) {
|
||||
throw new IOException("Illegal header: " + header);
|
||||
}
|
||||
|
||||
hyphen = 0;
|
||||
sb = new StringBuilder(1024);
|
||||
|
||||
// Determine the line break using the char after the last hyphen
|
||||
switch (is.read()) {
|
||||
case WS -> {} // skip whitespace
|
||||
case '\r' -> {
|
||||
c = is.read();
|
||||
if (c == '\n') {
|
||||
eol = '\n';
|
||||
} else {
|
||||
eol = '\r';
|
||||
sb.append((char) c);
|
||||
}
|
||||
}
|
||||
case '\n' -> eol = '\n';
|
||||
default ->
|
||||
throw new IOException("No EOL character found");
|
||||
}
|
||||
|
||||
// Read data until we find the first footer hyphen.
|
||||
do {
|
||||
switch (c = is.read()) {
|
||||
case -1 ->
|
||||
throw new EOFException("Incomplete header");
|
||||
case '-' -> hyphen++;
|
||||
case WS, '\t', '\r', '\n' -> {} // skip whitespace and tab
|
||||
default -> sb.append((char) c);
|
||||
}
|
||||
} while (hyphen == 0);
|
||||
|
||||
String data = sb.toString();
|
||||
|
||||
// Verify footer starts with 5 hyphens.
|
||||
do {
|
||||
switch (is.read()) {
|
||||
case '-' -> hyphen++;
|
||||
case -1 -> throw new EOFException("Input ended prematurely");
|
||||
default -> throw new IOException("Incomplete footer");
|
||||
}
|
||||
} while (hyphen < 5);
|
||||
|
||||
hyphen = 0;
|
||||
sb = new StringBuilder(64);
|
||||
sb.append("-----");
|
||||
|
||||
// Look for Complete header by looking for the end of the hyphens
|
||||
do {
|
||||
switch (c = is.read()) {
|
||||
case '-' -> hyphen++;
|
||||
case -1 -> throw new EOFException("Input ended prematurely");
|
||||
default -> sb.append((char) c);
|
||||
}
|
||||
} while (hyphen == 0);
|
||||
|
||||
// Verify ending with 5 hyphens.
|
||||
do {
|
||||
switch (is.read()) {
|
||||
case '-' -> hyphen++;
|
||||
case -1 -> throw new EOFException("Input ended prematurely");
|
||||
default -> throw new IOException("Incomplete footer");
|
||||
}
|
||||
} while (hyphen < 5);
|
||||
|
||||
while ((c = is.read()) != eol && c != -1 && c != WS) {
|
||||
// skip when eol is '\n', the line separator is likely "\r\n".
|
||||
if (c == '\r') {
|
||||
continue;
|
||||
}
|
||||
throw new IOException("Invalid PEM format: " +
|
||||
"No EOL char found in footer: 0x" +
|
||||
HexFormat.of().toHexDigits((byte) c));
|
||||
}
|
||||
|
||||
sb.append("-----");
|
||||
String footer = sb.toString();
|
||||
if (footer.length() < 14 || !footer.startsWith("-----END ") ||
|
||||
!footer.endsWith("-----")) {
|
||||
// Not an IOE because the read pointer is correctly at the end.
|
||||
throw new IOException("Illegal footer: " + footer);
|
||||
}
|
||||
|
||||
// Verify the object type in the header and the footer are the same.
|
||||
String headerType = header.substring(11, header.length() - 5);
|
||||
String footerType = footer.substring(9, footer.length() - 5);
|
||||
if (!headerType.equals(footerType)) {
|
||||
throw new IOException("Header and footer do not " +
|
||||
"match: " + headerType + " " + footerType);
|
||||
}
|
||||
|
||||
// If there was data before finding the 5 dashes of the PEM header,
|
||||
// backup 5 characters and save that data.
|
||||
byte[] preData = null;
|
||||
if (os.size() > 5) {
|
||||
preData = Arrays.copyOf(os.toByteArray(), os.size() - 5);
|
||||
}
|
||||
|
||||
return new PEMRecord(typeConverter(headerType), data, preData);
|
||||
}
|
||||
|
||||
public static PEMRecord readPEM(InputStream is) throws IOException {
|
||||
return readPEM(is, false);
|
||||
}
|
||||
|
||||
private static String pemEncoded(String type, String base64) {
|
||||
return
|
||||
"-----BEGIN " + type + "-----\r\n" +
|
||||
base64 + (!base64.endsWith("\n") ? "\r\n" : "") +
|
||||
"-----END " + type + "-----\r\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a String-based encoding based off the type. leadingData
|
||||
* is not used with this method.
|
||||
* @return PEM in a string
|
||||
*/
|
||||
public static String pemEncoded(String type, byte[] der) {
|
||||
if (b64Encoder == null) {
|
||||
b64Encoder = Base64.getMimeEncoder(64, CRLF);
|
||||
}
|
||||
return pemEncoded(type, b64Encoder.encodeToString(der));
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a String-based encoding based off the type. leadingData
|
||||
* is not used with this method.
|
||||
* @return PEM in a string
|
||||
*/
|
||||
public static String pemEncoded(PEMRecord pem) {
|
||||
String p = pem.pem().replaceAll("(.{64})", "$1\r\n");
|
||||
return pemEncoded(pem.type(), p);
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 1996, 2024, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 1996, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -36,7 +36,6 @@ import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.security.spec.X509EncodedKeySpec;
|
||||
import java.util.Objects;
|
||||
|
||||
import sun.security.util.HexDumpEncoder;
|
||||
import sun.security.util.*;
|
||||
@ -83,7 +82,8 @@ public class X509Key implements PublicKey, DerEncoder {
|
||||
* data is stored and transmitted losslessly, but no knowledge
|
||||
* about this particular algorithm is available.
|
||||
*/
|
||||
private X509Key(AlgorithmId algid, BitArray key) {
|
||||
@SuppressWarnings("this-escape")
|
||||
public X509Key(AlgorithmId algid, BitArray key) {
|
||||
this.algid = algid;
|
||||
setKey(key);
|
||||
encode();
|
||||
@ -100,7 +100,7 @@ public class X509Key implements PublicKey, DerEncoder {
|
||||
* Gets the key. The key may or may not be byte aligned.
|
||||
* @return a BitArray containing the key.
|
||||
*/
|
||||
protected BitArray getKey() {
|
||||
public BitArray getKey() {
|
||||
return (BitArray)bitStringKey.clone();
|
||||
}
|
||||
|
||||
@ -129,7 +129,7 @@ public class X509Key implements PublicKey, DerEncoder {
|
||||
algorithm = AlgorithmId.parse(in.data.getDerValue());
|
||||
try {
|
||||
subjectKey = buildX509Key(algorithm,
|
||||
in.data.getUnalignedBitString());
|
||||
in.data.getUnalignedBitString());
|
||||
|
||||
} catch (InvalidKeyException e) {
|
||||
throw new IOException("subject key, " + e.getMessage(), e);
|
||||
@ -154,7 +154,7 @@ public class X509Key implements PublicKey, DerEncoder {
|
||||
* @exception InvalidKeyException on invalid key encodings.
|
||||
*/
|
||||
protected void parseKeyBits() throws InvalidKeyException {
|
||||
encode();
|
||||
getEncodedInternal();
|
||||
}
|
||||
|
||||
/*
|
||||
@ -243,7 +243,7 @@ public class X509Key implements PublicKey, DerEncoder {
|
||||
/**
|
||||
* Returns the algorithm ID to be used with this key.
|
||||
*/
|
||||
public AlgorithmId getAlgorithmId() { return algid; }
|
||||
public AlgorithmId getAlgorithmId() { return algid; }
|
||||
|
||||
/**
|
||||
* Encode SubjectPublicKeyInfo sequence on the DER output stream.
|
||||
@ -260,7 +260,7 @@ public class X509Key implements PublicKey, DerEncoder {
|
||||
return getEncodedInternal().clone();
|
||||
}
|
||||
|
||||
public byte[] getEncodedInternal() {
|
||||
private byte[] getEncodedInternal() {
|
||||
byte[] encoded = encodedKey;
|
||||
if (encoded == null) {
|
||||
DerOutputStream out = new DerOutputStream();
|
||||
@ -314,7 +314,7 @@ public class X509Key implements PublicKey, DerEncoder {
|
||||
* @param val a DER-encoded X.509 SubjectPublicKeyInfo value
|
||||
* @exception InvalidKeyException on parsing errors.
|
||||
*/
|
||||
void decode(DerValue val) throws InvalidKeyException {
|
||||
public void decode(DerValue val) throws InvalidKeyException {
|
||||
try {
|
||||
if (val.tag != DerValue.tag_Sequence)
|
||||
throw new InvalidKeyException("invalid key format");
|
||||
|
@ -1549,3 +1549,12 @@ jdk.tls.alpnCharset=ISO_8859_1
|
||||
# security property value defined here.
|
||||
#
|
||||
#jdk.security.krb5.name.case.sensitive=false
|
||||
|
||||
#
|
||||
# Default algorithm for PEMEncoder Encrypted PKCS#8
|
||||
#
|
||||
# This property defines the default password-based encryption algorithm for
|
||||
# java.security.PEMEncoder when configured for encryption with the
|
||||
# withEncryption method.
|
||||
#
|
||||
jdk.epkcs8.defaultAlgorithm=PBEWithHmacSHA256AndAES_128
|
@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
|
||||
* Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
|
476
test/jdk/java/security/PEM/PEMData.java
Normal file
476
test/jdk/java/security/PEM/PEMData.java
Normal file
@ -0,0 +1,476 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
import javax.crypto.EncryptedPrivateKeyInfo;
|
||||
import java.security.DEREncodable;
|
||||
import java.security.KeyPair;
|
||||
import java.security.PEMRecord;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.security.interfaces.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Base64;
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Library class for PEMEncoderTest and PEMDecoderTest
|
||||
*/
|
||||
class PEMData {
|
||||
public static final Entry ecsecp256 = new Entry("ecsecp256",
|
||||
"""
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgkW3Jx561NlEgBnut
|
||||
KwDdi3cNwu7YYD/QtJ+9+AEBdoqhRANCAASL+REY4vvAI9M3gonaml5K3lRgHq5w
|
||||
+OO4oO0VNduC44gUN1nrk7/wdNSpL+xXNEX52Dsff+2RD/fop224ANvB
|
||||
-----END PRIVATE KEY-----
|
||||
""", KeyPair.class);
|
||||
|
||||
public static final Entry rsapriv = new Entry("rsapriv",
|
||||
"""
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBAOtjMnCzPy4jCeZb
|
||||
OdOvmvU3jl7+cvPFgL5MfqDCM5a8yI0yImg/hzibJJHLk3emUVBSnekgHvCqyGLW
|
||||
3qGR2DuBEaMy0mkg8hfKcSpHLaYjDYaspO27d2qtb6d1qtsPoPjJFjWFYeW6K463
|
||||
OHG654K5/2FcJgQdlLVyp3zCiQU/AgMBAAECgYEAwNkDkTv5rlX8nWLuLJV5kh/T
|
||||
H9a93SRZxw8qy5Bv7bZ7ZNrHP7uUkHbi7iPojKWRhwo43692SdzR0dCSk7LGgN9q
|
||||
CYvndsYR6gifVGBi0WF+St4+NdtcQ3VlNdsojy2BdIx0oC+r7i3bn+zc968O/kI+
|
||||
EgdgrMcjjFqyx6tMHpECQQD8TYPKGHyN7Jdy28llCoUX/sL/yZ2vIi5mnDAFE5ae
|
||||
KZQSkNAXG+8i9Qbs/Wdd5S3oZDqu+6DBn9gib80pYY05AkEA7tY59Oy8ka7nBlGP
|
||||
g6Wo1usF2bKqk8vjko9ioZQay7f86aB10QFcAjCr+cCUm16Lc9DwzWl02nNggRZa
|
||||
Jz8eNwJBAO+1zfLjFOPa14F/JHdlaVKE8EwKCFDuztsapd0M4Vtf8Zk6ERsDpU63
|
||||
Ml9T2zOwnM9g+whpdjDAZ59ATdJ1JrECQQDReJQ2SxeL0lGPCiOLu9RcQp7L81aF
|
||||
79G1bgp8WlAyEjlAkloiqEWRKiz7DDuKFR7Lwhognng9S+n87aS+PS57AkBh75t8
|
||||
6onPAs4hkm+63dfzCojvEkALevO8J3OVX7YS5q9J1r75wDn60Ob0Zh+iiorpx8Ob
|
||||
WqcWcoJqfdLEyBT+
|
||||
-----END PRIVATE KEY-----
|
||||
""", RSAPrivateKey.class);
|
||||
|
||||
public static final Entry rsaprivbc = new Entry("rsaprivbc",
|
||||
"""
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBAOtjMnCzPy4jCeZb
|
||||
OdOvmvU3jl7+cvPFgL5MfqDCM5a8yI0yImg/hzibJJHLk3emUVBSnekgHvCqyGLW
|
||||
3qGR2DuBEaMy0mkg8hfKcSpHLaYjDYaspO27d2qtb6d1qtsPoPjJFjWFYeW6K463
|
||||
OHG654K5/2FcJgQdlLVyp3zCiQU/AgMBAAECgYEAwNkDkTv5rlX8nWLuLJV5kh/T
|
||||
H9a93SRZxw8qy5Bv7bZ7ZNrHP7uUkHbi7iPojKWRhwo43692SdzR0dCSk7LGgN9q
|
||||
CYvndsYR6gifVGBi0WF+St4+NdtcQ3VlNdsojy2BdIx0oC+r7i3bn+zc968O/kI+
|
||||
EgdgrMcjjFqyx6tMHpECQQD8TYPKGHyN7Jdy28llCoUX/sL/yZ2vIi5mnDAFE5ae
|
||||
KZQSkNAXG+8i9Qbs/Wdd5S3oZDqu+6DBn9gib80pYY05AkEA7tY59Oy8ka7nBlGP
|
||||
g6Wo1usF2bKqk8vjko9ioZQay7f86aB10QFcAjCr+cCUm16Lc9DwzWl02nNggRZa
|
||||
Jz8eNwJBAO+1zfLjFOPa14F/JHdlaVKE8EwKCFDuztsapd0M4Vtf8Zk6ERsDpU63
|
||||
Ml9T2zOwnM9g+whpdjDAZ59ATdJ1JrECQQDReJQ2SxeL0lGPCiOLu9RcQp7L81aF
|
||||
79G1bgp8WlAyEjlAkloiqEWRKiz7DDuKFR7Lwhognng9S+n87aS+PS57AkBh75t8
|
||||
6onPAs4hkm+63dfzCojvEkALevO8J3OVX7YS5q9J1r75wDn60Ob0Zh+iiorpx8Ob
|
||||
WqcWcoJqfdLEyBT+
|
||||
-----END PRIVATE KEY-----
|
||||
""", RSAPrivateKey.class);
|
||||
|
||||
public static final Entry ec25519priv = new Entry("ed25519priv",
|
||||
"""
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MC4CAQAwBQYDK2VwBCIEIFFZsmD+OKk67Cigc84/2fWtlKsvXWLSoMJ0MHh4jI4I
|
||||
-----END PRIVATE KEY-----
|
||||
""", EdECPrivateKey.class);
|
||||
|
||||
public static final Entry rsapub = new Entry("rsapub",
|
||||
"""
|
||||
-----BEGIN PUBLIC KEY-----
|
||||
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDrYzJwsz8uIwnmWznTr5r1N45e
|
||||
/nLzxYC+TH6gwjOWvMiNMiJoP4c4mySRy5N3plFQUp3pIB7wqshi1t6hkdg7gRGj
|
||||
MtJpIPIXynEqRy2mIw2GrKTtu3dqrW+ndarbD6D4yRY1hWHluiuOtzhxuueCuf9h
|
||||
XCYEHZS1cqd8wokFPwIDAQAB
|
||||
-----END PUBLIC KEY-----
|
||||
""", RSAPublicKey.class);
|
||||
|
||||
public static final Entry rsapubbc = new Entry("rsapubbc",
|
||||
"""
|
||||
-----BEGIN PUBLIC KEY-----
|
||||
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDrYzJwsz8uIwnmWznTr5r1N45e
|
||||
/nLzxYC+TH6gwjOWvMiNMiJoP4c4mySRy5N3plFQUp3pIB7wqshi1t6hkdg7gRGj
|
||||
MtJpIPIXynEqRy2mIw2GrKTtu3dqrW+ndarbD6D4yRY1hWHluiuOtzhxuueCuf9h
|
||||
XCYEHZS1cqd8wokFPwIDAQAB
|
||||
-----END PUBLIC KEY-----
|
||||
""", RSAPublicKey.class);
|
||||
|
||||
public static final Entry ecsecp256pub = new Entry("ecsecp256pub", """
|
||||
-----BEGIN PUBLIC KEY-----
|
||||
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEi/kRGOL7wCPTN4KJ2ppeSt5UYB6u
|
||||
cPjjuKDtFTXbguOIFDdZ65O/8HTUqS/sVzRF+dg7H3/tkQ/36KdtuADbwQ==
|
||||
-----END PUBLIC KEY-----
|
||||
""", ECPublicKey.class);
|
||||
|
||||
// EC key with explicit parameters -- Not currently supported by SunEC
|
||||
public static final String pubec_explicit = """
|
||||
-----BEGIN PUBLIC KEY-----
|
||||
MIIBSzCCAQMGByqGSM49AgEwgfcCAQEwLAYHKoZIzj0BAQIhAP////8AAAABAAAA
|
||||
AAAAAAAAAAAA////////////////MFsEIP////8AAAABAAAAAAAAAAAAAAAA////
|
||||
///////////8BCBaxjXYqjqT57PrvVV2mIa8ZR0GsMxTsPY7zjw+J9JgSwMVAMSd
|
||||
NgiG5wSTamZ44ROdJreBn36QBEEEaxfR8uEsQkf4vOblY6RA8ncDfYEt6zOg9KE5
|
||||
RdiYwpZP40Li/hp/m47n60p8D54WK84zV2sxXs7LtkBoN79R9QIhAP////8AAAAA
|
||||
//////////+85vqtpxeehPO5ysL8YyVRAgEBA0IABIv5ERji+8Aj0zeCidqaXkre
|
||||
VGAernD447ig7RU124LjiBQ3WeuTv/B01Kkv7Fc0RfnYOx9/7ZEP9+inbbgA28E=
|
||||
-----END PUBLIC KEY-----
|
||||
""";
|
||||
|
||||
public static final Entry oasbcpem = new Entry("oasbcpem",
|
||||
"""
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIIDCAIBATANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBAOtjMnCzPy4jCeZbOdOvmvU3jl7+
|
||||
cvPFgL5MfqDCM5a8yI0yImg/hzibJJHLk3emUVBSnekgHvCqyGLW3qGR2DuBEaMy0mkg8hfKcSpH
|
||||
LaYjDYaspO27d2qtb6d1qtsPoPjJFjWFYeW6K463OHG654K5/2FcJgQdlLVyp3zCiQU/AgMBAAEC
|
||||
gYEAwNkDkTv5rlX8nWLuLJV5kh/TH9a93SRZxw8qy5Bv7bZ7ZNrHP7uUkHbi7iPojKWRhwo43692
|
||||
SdzR0dCSk7LGgN9qCYvndsYR6gifVGBi0WF+St4+NdtcQ3VlNdsojy2BdIx0oC+r7i3bn+zc968O
|
||||
/kI+EgdgrMcjjFqyx6tMHpECQQD8TYPKGHyN7Jdy28llCoUX/sL/yZ2vIi5mnDAFE5aeKZQSkNAX
|
||||
G+8i9Qbs/Wdd5S3oZDqu+6DBn9gib80pYY05AkEA7tY59Oy8ka7nBlGPg6Wo1usF2bKqk8vjko9i
|
||||
oZQay7f86aB10QFcAjCr+cCUm16Lc9DwzWl02nNggRZaJz8eNwJBAO+1zfLjFOPa14F/JHdlaVKE
|
||||
8EwKCFDuztsapd0M4Vtf8Zk6ERsDpU63Ml9T2zOwnM9g+whpdjDAZ59ATdJ1JrECQQDReJQ2SxeL
|
||||
0lGPCiOLu9RcQp7L81aF79G1bgp8WlAyEjlAkloiqEWRKiz7DDuKFR7Lwhognng9S+n87aS+PS57
|
||||
AkBh75t86onPAs4hkm+63dfzCojvEkALevO8J3OVX7YS5q9J1r75wDn60Ob0Zh+iiorpx8ObWqcW
|
||||
coJqfdLEyBT+gYGNADCBiQKBgQDrYzJwsz8uIwnmWznTr5r1N45e/nLzxYC+TH6gwjOWvMiNMiJo
|
||||
P4c4mySRy5N3plFQUp3pIB7wqshi1t6hkdg7gRGjMtJpIPIXynEqRy2mIw2GrKTtu3dqrW+ndarb
|
||||
D6D4yRY1hWHluiuOtzhxuueCuf9hXCYEHZS1cqd8wokFPwIDAQAB
|
||||
-----END PRIVATE KEY-----
|
||||
""", KeyPair.class);
|
||||
|
||||
public static final Entry oasrfc8410 = new Entry("oasrfc8410",
|
||||
"""
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MHICAQEwBQYDK2VwBCIEINTuctv5E1hK1bbY8fdp+K06/nwoy/HU++CXqI9EdVhC
|
||||
oB8wHQYKKoZIhvcNAQkJFDEPDA1DdXJkbGUgQ2hhaXJzgSEAGb9ECWmEzf6FQbrB
|
||||
Z9w7lshQhqowtrbLDFw4rXAxZuE=
|
||||
-----END PRIVATE KEY-----
|
||||
""", KeyPair.class);
|
||||
|
||||
public static final Entry rsaOpenSSL = new Entry("rsaOpenSSL",
|
||||
"""
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEowIBAAKCAQEAqozTLan1qFcOCWnS63jXQn5lLyGOKDv3GM11n2zkGGrChayj
|
||||
cSzB2KTlDmN9NgOyFdqGNWbSgdmXR5ToHGHYwaKubJoQIoPQcsipWDI156d3+X/8
|
||||
BxCGY8l5nYwvS4olOXc+2kEjeFF1eamnm9IQ5DHZfaFPl0ri4Yfm1YHBAbt/7HvF
|
||||
3MBjgBj1xSsSFLW4O6ws6guRVGDfKBVyyRNUhRTbSua/nEz0wAjxF2PWT+ZTHS6M
|
||||
0siYwVTuPI4/n4ItoYoahvGb9JskkXP+bc/QZJCTFYdyxF5tKqVMSdYaJTxop02p
|
||||
Jo3oeafVKSlBrr0K731xgNBKqBud44aKT5R96QIDAQABAoIBAQCD9Q/T7gOvayPm
|
||||
LqXOISJURV1emRTXloX5/8Y5QtQ8/CVjrg6Lm3ikefjsKBgR+cwJUpmyqcrIQyXk
|
||||
cZchlqdSMt/IEW/YdKqMlStJnRfOE+ok9lx2ztdcT9+0AWn6hXmFu/i6f9nE1yoQ
|
||||
py6SxnbhSJyhsnTVd1CR9Uep/InsHvYW/15WlVMD1VuCSIt9sefqXwavbAfBaqbn
|
||||
mjwBB/ulsqKhHSuRq/QWqlj+jyGqhhYmTguC1Qwt0woDbThiHtK+suCTAlGBj/A+
|
||||
IZ1U9d+VsHBcWDKBkxmlKWcJAGR3xXiKKy9vfzC+DU7L99kgay80VZarDyXgiy78
|
||||
9xMMzRMBAoGBANoxnZhu1bUFtLqTJ1HfDm6UB+1zVd2Mu4DXYdy/AHjoaCLp05OQ
|
||||
0ZeyhO/eXPT+eGpzCxkWD7465KO/QDfnp54p/NS73jaJVdWQHBhzJx1MymqURy3N
|
||||
JQeW4+ojzwSmVXcrs7Og6EBa4L+PWLpMLW2kODniCY+vp9f5LS6m8UPJAoGBAMgZ
|
||||
4rBw7B9YFZZW/EE4eos4Q7KtA5tEP6wvCq04oxfiSytWXifYX0ToPp0CHhZlWOxk
|
||||
v9a/BDGqM7AxAQJs7mmIvT5AT2V1w7oTbFPnnAo6pQtLcfaxdFFqr0h6t0sXSOKC
|
||||
rQeZAqqFqwuOyP7vT0goGlBruHkwS21NKkzCyzkhAoGAc2JjhbWu+8Cdt0CUPX5o
|
||||
ol9T5eTlFnkSuuqrTNIQzN+SGkxu341o2QDFvhdoLwLW6OwXhVeeUanROSqtKiMu
|
||||
B70Kf/EtbMephXtk8CUNHTh7nmr1TSo8F8xakHoJQts3PQL2T9qal1W3nnWOpU4d
|
||||
g+qg9TMsfTiV2OdjVlVgJskCgYBSnjV1qjojuue22hVvDFW0c7en5z2M9wHfItEi
|
||||
sjbMnrdwnklj5Dd5qPZpNz2a+59ag0Kd9OJTazXKMoF7MeTCGB4ivMTLXHNCudBJ
|
||||
WGCZ7JrGbhEQzTX8g7L5lwlk7KlANLoiX++03lm//OVKNR6j6ULsH33cM6+A4pJr
|
||||
fSYRYQKBgCr9iMTmL0x+n6AmMNecR+MhDxi99Oy0s2EBAYqN9g/8yNgwM4KR0cjz
|
||||
EcgIOtkvoTrJ9Cquvuj+O7/d2yNoH0SZQ4IYJKq47/Z4kKhwXzJnBCCCBKgkjfub
|
||||
RTQSNnSEgTaBD29l7FrhNRHX9lIKFZ23caCTBS6o3q3+KgPbq7ao
|
||||
-----END RSA PRIVATE KEY-----
|
||||
""", RSAPrivateKey.class);
|
||||
|
||||
static final Entry ed25519ep8 = new Entry("ed25519ep8",
|
||||
"""
|
||||
-----BEGIN ENCRYPTED PRIVATE KEY-----
|
||||
MIGqMGYGCSqGSIb3DQEFDTBZMDgGCSqGSIb3DQEFDDArBBRyYnoNyrcqvubzch00
|
||||
jyuAb5YizgICEAACARAwDAYIKoZIhvcNAgkFADAdBglghkgBZQMEAQIEEM8BgEgO
|
||||
vdMyi46+Dw7cOjwEQLtx5ME0NOOo7vlCGm3H/4j+Tf5UXrMb1UrkPjqc8OiLbC0n
|
||||
IycFtI70ciPjgwDSjtCcPxR8fSxJPrm2yOJsRVo=
|
||||
-----END ENCRYPTED PRIVATE KEY-----
|
||||
""", EdECPrivateKey.class, "fish".toCharArray());
|
||||
|
||||
// This is not meant to be decrypted and to stay as an EKPI
|
||||
static final Entry ed25519ekpi = new Entry("ed25519ekpi",
|
||||
ed25519ep8.pem(), EncryptedPrivateKeyInfo.class, null);
|
||||
|
||||
static final Entry rsaCert = new Entry("rsaCert",
|
||||
"""
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIErDCCApQCCQD7ndjWbI/x0DANBgkqhkiG9w0BAQsFADAXMRUwEwYDVQQDDAxQ
|
||||
RU0gVGVzdCBSU0EwIBcNMjQwMTA5MjMzNDIwWhgPMjA1MTA1MjYyMzM0MjBaMBcx
|
||||
FTATBgNVBAMMDFBFTSBUZXN0IFJTQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCC
|
||||
AgoCggIBAKgO/Pciro8xn5iNjcVCR4IuXP+V1PNATtKAlMbWzwGVOupKgRcNeRbA
|
||||
N9RlljxSgEChIWs0/DB9VsAw1wCIVeuIVxv0ZvhVAcuD8Yyl58eev1rptsSJhTkN
|
||||
YJFxEPSP2kfWDxS21ltbg1bnY/c1SQbzWawDLJN16G+ICzQXo68UB5fCZV9Ugfgf
|
||||
9USPkCiC6aFt+RT7eQaN/JrjtCm+mFf4VbK7jYW7D8AfjviEY1HQCnPoTjHBxdy+
|
||||
o5s4aIOx1Wuu9wMoGuLXgY3do5/OSDCfByk7rc1drQB9GOKf2gkR8PL9TjK+R3Lq
|
||||
wCA0a3jlCBiGPlH3oeZJrnp7jhAh/tVxbsd7yIdhQnasbiTfhew132AdPXoQE+ic
|
||||
PFoh8MMtG1bdzt8EbvePC3GOjeyIP6f2Ixrh3B6wXzzYmJqBwON+X8TLQolcI1pa
|
||||
Q7AUz5BScy3lO9nyJE/FJkX+Mr6n7WCdudCrQNP+0M845UvkgFyf4FcM7uUVugBm
|
||||
AXy7sCqZgTeLdqHyTElMCoWzBa3MHKyiSCh8GUJH+I1yBY1gG95j3tITIOFvbZrk
|
||||
vDiMwNtV9T6Ta2mb0+38GfKjbI6PF4DVrzB6xc7Q6/GwyhOb86YLOLlEHJfhuc+C
|
||||
Pdy8hQrrulm2jiCO/skvHucABNJ2CENyWa7ljNJkcN6GNTziz4AhAgMBAAEwDQYJ
|
||||
KoZIhvcNAQELBQADggIBAKFQE2AgYgc7/xzwveUAiZ55tfcds07UnazLCOdpz+JJ
|
||||
W4MOt/1Qi9mUylqDEymfNZVLPd2dEjB4wJ57XBUjL+kXkH1SocuskxQPf05iz5zT
|
||||
pEwg2fTmU73ilKMs5Q113nBnL9ZZtlRKCh1Oc5LvLW799uVXnU4UdSpWOBU9ePGY
|
||||
+H1wUKf+e0/BkveQsZERYcamH9O9U/+h+bbhr3GpT1AVnuDRyF28OvRwARDCOVyy
|
||||
ifh+xCR3WCnNcgfwCoH6cE1aXDKHchlAAZtvjc1lLud7/ECIg+15keVfTYk4HEbH
|
||||
j/lprxyH7y99lMmRLQpnTve54RrZGGmg51UD7OmwPHLMGibfQkw6QgdNsggIYD6p
|
||||
L91spgRRB+i4PTovocndOMR2RYgQEelGNqv8MsoUC7oRNxPCHxIEGuUPH1Vf3jnk
|
||||
mTHbVzpjy57UtfcYp1uBFDf8WoWO1Mi6oXRw2YQA1YSMm1+3ftphxydcbRuBlS7O
|
||||
6Iiqk6XlFG9Dpd2jjAQQzJGtnC0QDgGz6/KGp1bGEhRnOWju07eLWvPbyaX5zeSh
|
||||
8gOYV33zkPhziWJt4uFMFIi7N2DLEk5UVZv1KTLZlfPl55DRs7j/Sb4vKHpB17AO
|
||||
meVknxVvifDVY0TIz57t28Accsk6ClBCxNPluPU/8YLGAZJYsdDXjGcndQ13s5G7
|
||||
-----END CERTIFICATE-----
|
||||
""", X509Certificate.class);
|
||||
|
||||
static final Entry ecCert = new Entry("ecCert",
|
||||
"""
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIBFzCBvgIJAOGVk/ky59ojMAoGCCqGSM49BAMCMBMxETAPBgNVBAMMCFBFTSB0
|
||||
ZXN0MCAXDTI0MDEwOTIzMzEwNloYDzIwNTEwNTI2MjMzMTA2WjATMREwDwYDVQQD
|
||||
DAhQRU0gdGVzdDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABGYI0jD7JZzw4RYD
|
||||
y9DCfaYNz0CHrpr9gJU5NXe6czvuNBdAOl/lJGQ1pqpEQSQaMDII68obvQyQQyFY
|
||||
lU3G9QAwCgYIKoZIzj0EAwIDSAAwRQIgMwYld7aBzkcRt9mn27YOed5+n0xN1y8Q
|
||||
VEcFjLI/tBYCIQDU3szDZ/PK2mUZwtgQxLqHdh+f1JY0UwQS6M8QUvoDHw==
|
||||
-----END CERTIFICATE-----
|
||||
""", X509Certificate.class);
|
||||
|
||||
// EC cert with explicit parameters -- Not currently supported by SunEC
|
||||
static final String ecCertEX = """
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIICrDCCAjMCCQDKAlI7uc1CVDAKBggqhkjOPQQDAjATMREwDwYDVQQDDAhQRU0g
|
||||
dGVzdDAgFw0yNDAxMDkyMzIxNTlaGA8yMDUxMDUyNjIzMjE1OVowEzERMA8GA1UE
|
||||
AwwIUEVNIHRlc3QwggHMMIIBZAYHKoZIzj0CATCCAVcCAQEwPAYHKoZIzj0BAQIx
|
||||
AP/////////////////////////////////////////+/////wAAAAAAAAAA////
|
||||
/zB7BDD//////////////////////////////////////////v////8AAAAAAAAA
|
||||
AP////wEMLMxL6fiPufkmI4Fa+P4LRkYHZxu/oFBEgMUCI9QE4daxlY5jYou0Z0q
|
||||
hcjt0+wq7wMVAKM1kmqjGaJ6HQCJamdzpIJ6zaxzBGEEqofKIr6LBTeOscce8yCt
|
||||
dG4dO2KLp5uYWfdB4IJUKjhVAvJdv1UpbDpUXjhydgq3NhfeSpYmLG9dnpi/kpLc
|
||||
Kfj0Hb0omhR86doxE7XwuMAKYLHOHX6BnXpDHXyQ6g5fAjEA////////////////
|
||||
////////////////x2NNgfQ3Ld9YGg2ySLCneuzsGWrMxSlzAgEBA2IABO+IbTh6
|
||||
WqyzmxdCeJ0uUQ2v2jKxRuCKRyPlYAnpBmmQypsRS+GBdbBa0Mu6MTnVJh5uvqXn
|
||||
q7IuHVEiE3EFKw0DNW30nINuQg6lTv6PgN/4nYBqsl5FQgzk2SYN3bw+7jAKBggq
|
||||
hkjOPQQDAgNnADBkAjATCnbbn3CgPRPi9Nym0hKpBAXc30D4eVB3mz8snK0oKU0+
|
||||
VP3F0EWcyM2QDSZCXIgCMHWknAhIGFTHxqypYUV8eAd3SY7ujZ6EPR0uG//csBWG
|
||||
IqHcgr8slqi35ycQn5yMsQ==
|
||||
-----END CERTIFICATE-----
|
||||
""";
|
||||
|
||||
static final Entry ecsecp384 = new Entry("ecsecp384",
|
||||
"""
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDBVS52ZSKZ0oES7twD2
|
||||
GGwRIVu3uHlGIwlu0xzFe7sgIPntca2bHfYMhgGxrlCm0q+hZANiAAQNWgwWfLX8
|
||||
8pYVjvwbfvDF9f+Oa9w6JjrfpWwFAUI6b1OPgrNUh+yXtUXnQNXnfUcIu0Os53bM
|
||||
8fTqPkQl6RyWEDHeXqJK8zTBHMeBq9nLfDPSbzQgLDyC64Orn0D8exM=
|
||||
-----END PRIVATE KEY-----
|
||||
""", KeyPair.class);
|
||||
|
||||
public static final Entry ecCSR = new Entry("ecCSR",
|
||||
"""
|
||||
-----BEGIN CERTIFICATE REQUEST-----
|
||||
MIICCTCCAbACAQAwRTELMAkGA1UEBhMCVVMxDTALBgNVBAgMBFRlc3QxFDASBgNV
|
||||
BAcMC1NhbnRhIENsYXJhMREwDwYDVQQDDAhUZXN0IENTUjCCAUswggEDBgcqhkjO
|
||||
PQIBMIH3AgEBMCwGByqGSM49AQECIQD/////AAAAAQAAAAAAAAAAAAAAAP//////
|
||||
/////////zBbBCD/////AAAAAQAAAAAAAAAAAAAAAP///////////////AQgWsY1
|
||||
2Ko6k+ez671VdpiGvGUdBrDMU7D2O848PifSYEsDFQDEnTYIhucEk2pmeOETnSa3
|
||||
gZ9+kARBBGsX0fLhLEJH+Lzm5WOkQPJ3A32BLeszoPShOUXYmMKWT+NC4v4af5uO
|
||||
5+tKfA+eFivOM1drMV7Oy7ZAaDe/UfUCIQD/////AAAAAP//////////vOb6racX
|
||||
noTzucrC/GMlUQIBAQNCAAT3UJgGXD7xMwFSzBnkhsEXz3eJLjIE0HTP1Ax6x7QX
|
||||
G3/+Z/qgOZ6UQCxeHOWMEgF1Ufc/tZkzgbvxWJ6gokeToBUwEwYJKoZIhvcNAQkH
|
||||
MQYMBGZpc2gwCgYIKoZIzj0EAwIDRwAwRAIgUBTdrMDE4BqruYRh1rRyKQBf48WR
|
||||
kIX8R4dBK9h1VRcCIEBR2Mzvku/huTbWTwKVlXBZeEmwIlxKwpRepPtViXcW
|
||||
-----END CERTIFICATE REQUEST-----
|
||||
""", PEMRecord.class);
|
||||
|
||||
public static final String preData = "TEXT BLAH TEXT BLAH" +
|
||||
System.lineSeparator();
|
||||
public static final String postData = "FINISHED" + System.lineSeparator();
|
||||
|
||||
public static final Entry ecCSRWithData = new Entry("ecCSRWithData",
|
||||
preData + """
|
||||
-----BEGIN CERTIFICATE REQUEST-----
|
||||
MIICCTCCAbACAQAwRTELMAkGA1UEBhMCVVMxDTALBgNVBAgMBFRlc3QxFDASBgNV
|
||||
BAcMC1NhbnRhIENsYXJhMREwDwYDVQQDDAhUZXN0IENTUjCCAUswggEDBgcqhkjO
|
||||
PQIBMIH3AgEBMCwGByqGSM49AQECIQD/////AAAAAQAAAAAAAAAAAAAAAP//////
|
||||
/////////zBbBCD/////AAAAAQAAAAAAAAAAAAAAAP///////////////AQgWsY1
|
||||
2Ko6k+ez671VdpiGvGUdBrDMU7D2O848PifSYEsDFQDEnTYIhucEk2pmeOETnSa3
|
||||
gZ9+kARBBGsX0fLhLEJH+Lzm5WOkQPJ3A32BLeszoPShOUXYmMKWT+NC4v4af5uO
|
||||
5+tKfA+eFivOM1drMV7Oy7ZAaDe/UfUCIQD/////AAAAAP//////////vOb6racX
|
||||
noTzucrC/GMlUQIBAQNCAAT3UJgGXD7xMwFSzBnkhsEXz3eJLjIE0HTP1Ax6x7QX
|
||||
G3/+Z/qgOZ6UQCxeHOWMEgF1Ufc/tZkzgbvxWJ6gokeToBUwEwYJKoZIhvcNAQkH
|
||||
MQYMBGZpc2gwCgYIKoZIzj0EAwIDRwAwRAIgUBTdrMDE4BqruYRh1rRyKQBf48WR
|
||||
kIX8R4dBK9h1VRcCIEBR2Mzvku/huTbWTwKVlXBZeEmwIlxKwpRepPtViXcW
|
||||
-----END CERTIFICATE REQUEST-----
|
||||
""" + postData, PEMRecord.class);
|
||||
|
||||
final static Pattern CR = Pattern.compile("\r");
|
||||
final static Pattern LF = Pattern.compile("\n");
|
||||
final static Pattern LSDEFAULT = Pattern.compile(System.lineSeparator());
|
||||
|
||||
|
||||
public record Entry(String name, String pem, Class clazz, char[] password,
|
||||
byte[] der) {
|
||||
|
||||
public Entry(String name, String pem, Class clazz, char[] password,
|
||||
byte[] der) {
|
||||
this.name = name;
|
||||
this.pem = pem;
|
||||
this.clazz = clazz;
|
||||
this.password = password;
|
||||
if (pem != null && pem.length() > 0) {
|
||||
String[] pemtext = pem.split("-----");
|
||||
this.der = Base64.getMimeDecoder().decode(pemtext[2]);
|
||||
} else {
|
||||
this.der = null;
|
||||
}
|
||||
}
|
||||
Entry(String name, String pem, Class clazz, char[] password) {
|
||||
this(name, pem, clazz, password, null);
|
||||
}
|
||||
|
||||
Entry(String name, String pem, Class clazz) {
|
||||
this(name, pem, clazz, null, null);
|
||||
}
|
||||
|
||||
public Entry newClass(String name, Class c) {
|
||||
return new Entry(name, pem, c, password);
|
||||
}
|
||||
|
||||
public Entry newClass(Class c) {
|
||||
return newClass(name, c);
|
||||
}
|
||||
|
||||
Entry makeCRLF(String name) {
|
||||
return new Entry(name,
|
||||
Pattern.compile(System.lineSeparator()).matcher(pem).replaceAll("\r\n"),
|
||||
clazz, password());
|
||||
}
|
||||
|
||||
Entry makeCR(String name) {
|
||||
return new Entry(name,
|
||||
Pattern.compile(System.lineSeparator()).matcher(pem).replaceAll("\r"),
|
||||
clazz, password());
|
||||
}
|
||||
|
||||
Entry makeNoCRLF(String name) {
|
||||
return new Entry(name,
|
||||
LF.matcher(CR.matcher(pem).replaceAll("")).
|
||||
replaceAll(""),
|
||||
clazz, password());
|
||||
}
|
||||
}
|
||||
|
||||
static public Entry getEntry(String varname) {
|
||||
return getEntry(passList, varname);
|
||||
}
|
||||
|
||||
static public Entry getEntry(List<Entry> list, String varname) {
|
||||
for (Entry entry : list) {
|
||||
if (entry.name.compareToIgnoreCase(varname) == 0) {
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static List<Entry> passList = new ArrayList<>();
|
||||
static List<Entry> entryList = new ArrayList<>();
|
||||
static List<Entry> pubList = new ArrayList<>();
|
||||
static List<Entry> privList = new ArrayList<>();
|
||||
static List<Entry> oasList = new ArrayList<>();
|
||||
static List<Entry> certList = new ArrayList<>();
|
||||
static List<Entry> encryptedList = new ArrayList<>();
|
||||
static List<Entry> failureEntryList = new ArrayList<>();
|
||||
|
||||
static {
|
||||
pubList.add(rsapub);
|
||||
pubList.add(rsapubbc);
|
||||
pubList.add(ecsecp256pub.makeCR("ecsecp256pub-r"));
|
||||
pubList.add(ecsecp256pub.makeCRLF("ecsecp256pub-rn"));
|
||||
privList.add(rsapriv);
|
||||
privList.add(rsaprivbc);
|
||||
privList.add(ecsecp256);
|
||||
privList.add(ecsecp384);
|
||||
privList.add(ec25519priv);
|
||||
privList.add(ed25519ekpi); // The non-EKPI version needs decryption
|
||||
privList.add(rsaOpenSSL);
|
||||
oasList.add(oasrfc8410);
|
||||
oasList.add(oasbcpem);
|
||||
|
||||
certList.add(rsaCert);
|
||||
certList.add(ecCert);
|
||||
|
||||
entryList.addAll(pubList);
|
||||
entryList.addAll(privList);
|
||||
entryList.addAll(oasList);
|
||||
entryList.addAll(certList);
|
||||
|
||||
encryptedList.add(ed25519ep8);
|
||||
|
||||
passList.addAll(entryList);
|
||||
passList.addAll(encryptedList);
|
||||
|
||||
failureEntryList.add(new Entry("emptyPEM", "", DEREncodable.class, null));
|
||||
failureEntryList.add(new Entry("nullPEM", null, DEREncodable.class, null));
|
||||
}
|
||||
|
||||
static void checkResults(PEMData.Entry entry, String result) {
|
||||
try {
|
||||
checkResults(entry.pem(), result);
|
||||
} catch (AssertionError e) {
|
||||
throw new AssertionError("Encoder PEM mismatch " +
|
||||
entry.name(), e);
|
||||
}
|
||||
}
|
||||
|
||||
static void checkResults(String expected, String result) {
|
||||
// The below matches the \r\n generated PEM with the PEM passed
|
||||
// into the test.
|
||||
String pem = LF.matcher(CR.matcher(expected).replaceAll("")).
|
||||
replaceAll("");
|
||||
result = LF.matcher(CR.matcher(result).replaceAll("")).
|
||||
replaceAll("");
|
||||
try {
|
||||
if (pem.compareTo(result) != 0) {
|
||||
System.out.println("expected:\n" + pem);
|
||||
System.out.println("generated:\n" + result);
|
||||
indexDiff(pem, result);
|
||||
}
|
||||
} catch (AssertionError e) {
|
||||
throw new AssertionError("Encoder PEM mismatch ");
|
||||
}
|
||||
}
|
||||
|
||||
static void indexDiff(String a, String b) {
|
||||
String lenerr = "";
|
||||
int len = a.length();
|
||||
int lenb = b.length();
|
||||
if (len != lenb) {
|
||||
lenerr = ": Length mismatch: " + len + " vs " + lenb;
|
||||
len = Math.min(len, lenb);
|
||||
}
|
||||
for (int i = 0; i < len; i++) {
|
||||
if (a.charAt(i) != b.charAt(i)) {
|
||||
throw new AssertionError("Char mistmatch, index #" + i +
|
||||
" (" + a.charAt(i) + " vs " + b.charAt(i) + ")" + lenerr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
510
test/jdk/java/security/PEM/PEMDecoderTest.java
Normal file
510
test/jdk/java/security/PEM/PEMDecoderTest.java
Normal file
@ -0,0 +1,510 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @bug 8298420
|
||||
* @modules java.base/sun.security.pkcs
|
||||
* java.base/sun.security.util
|
||||
* @summary Testing basic PEM API decoding
|
||||
* @enablePreview
|
||||
*/
|
||||
|
||||
import javax.crypto.EncryptedPrivateKeyInfo;
|
||||
import java.io.*;
|
||||
import java.lang.Class;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.*;
|
||||
import java.security.cert.X509CRL;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.security.interfaces.*;
|
||||
import java.security.spec.PKCS8EncodedKeySpec;
|
||||
import java.security.spec.X509EncodedKeySpec;
|
||||
import java.util.*;
|
||||
import java.util.Arrays;
|
||||
|
||||
import sun.security.util.Pem;
|
||||
|
||||
public class PEMDecoderTest {
|
||||
|
||||
static HexFormat hex = HexFormat.of();
|
||||
|
||||
public static void main(String[] args) throws IOException {
|
||||
System.out.println("Decoder test:");
|
||||
PEMData.entryList.forEach(PEMDecoderTest::test);
|
||||
System.out.println("Decoder test returning DEREncodable class:");
|
||||
PEMData.entryList.forEach(entry -> test(entry, DEREncodable.class));
|
||||
System.out.println("Decoder test with encrypted PEM:");
|
||||
PEMData.encryptedList.forEach(PEMDecoderTest::testEncrypted);
|
||||
System.out.println("Decoder test with OAS:");
|
||||
testTwoKeys();
|
||||
System.out.println("Decoder test RSA PEM setting RSAKey.class returned:");
|
||||
test(PEMData.rsapriv, RSAKey.class);
|
||||
System.out.println("Decoder test failures:");
|
||||
PEMData.failureEntryList.forEach(PEMDecoderTest::testFailure);
|
||||
System.out.println("Decoder test ecsecp256 PEM asking for ECPublicKey.class returned:");
|
||||
testFailure(PEMData.ecsecp256, ECPublicKey.class);
|
||||
System.out.println("Decoder test rsapriv PEM setting P8EKS.class returned:");
|
||||
testClass(PEMData.rsapriv, RSAPrivateKey.class);
|
||||
System.out.println("Decoder test rsaOpenSSL P1 PEM asking for RSAPublicKey.class returned:");
|
||||
testFailure(PEMData.rsaOpenSSL, RSAPublicKey.class);
|
||||
System.out.println("Decoder test rsapub PEM asking X509EKS.class returned:");
|
||||
testClass(PEMData.rsapub, X509EncodedKeySpec.class, true);
|
||||
System.out.println("Decoder test rsapriv PEM asking X509EKS.class returned:");
|
||||
testClass(PEMData.rsapriv, X509EncodedKeySpec.class, false);
|
||||
System.out.println("Decoder test RSAcert PEM asking X509EKS.class returned:");
|
||||
testClass(PEMData.rsaCert, X509EncodedKeySpec.class, false);
|
||||
System.out.println("Decoder test OAS RFC PEM asking PrivateKey.class returned:");
|
||||
testClass(PEMData.oasrfc8410, PrivateKey.class, true);
|
||||
testClass(PEMData.oasrfc8410, PublicKey.class, true);
|
||||
System.out.println("Decoder test ecsecp256:");
|
||||
testFailure(PEMData.ecsecp256pub.makeNoCRLF("pubecpem-no"));
|
||||
System.out.println("Decoder test RSAcert with decryption Decoder:");
|
||||
PEMDecoder d = PEMDecoder.of().withDecryption("123".toCharArray());
|
||||
d.decode(PEMData.rsaCert.pem());
|
||||
System.out.println("Decoder test ecsecp256 private key with decryption Decoder:");
|
||||
((KeyPair) d.decode(PEMData.ecsecp256.pem())).getPrivate();
|
||||
System.out.println("Decoder test ecsecp256 to P8EKS:");
|
||||
d.decode(PEMData.ecsecp256.pem(), PKCS8EncodedKeySpec.class);
|
||||
|
||||
System.out.println("Checking if decode() returns the same encoding:");
|
||||
PEMData.privList.forEach(PEMDecoderTest::testDERCheck);
|
||||
PEMData.oasList.forEach(PEMDecoderTest::testDERCheck);
|
||||
|
||||
System.out.println("Check a Signature/Verify op is successful:");
|
||||
PEMData.privList.forEach(PEMDecoderTest::testSignature);
|
||||
PEMData.oasList.forEach(PEMDecoderTest::testSignature);
|
||||
|
||||
System.out.println("Checking if ecCSR:");
|
||||
test(PEMData.ecCSR);
|
||||
System.out.println("Checking if ecCSR with preData:");
|
||||
DEREncodable result = PEMDecoder.of().decode(PEMData.ecCSRWithData.pem(), PEMRecord.class);
|
||||
if (result instanceof PEMRecord rec) {
|
||||
if (PEMData.preData.compareTo(new String(rec.leadingData())) != 0) {
|
||||
System.err.println("expected: " + PEMData.preData);
|
||||
System.err.println("received: " + new String(rec.leadingData()));
|
||||
throw new AssertionError("ecCSRWithData preData wrong");
|
||||
}
|
||||
if (rec.pem().lastIndexOf("F") > rec.pem().length() - 5) {
|
||||
System.err.println("received: " + rec.pem());
|
||||
throw new AssertionError("ecCSRWithData: " +
|
||||
"End of PEM data has an unexpected character");
|
||||
}
|
||||
} else {
|
||||
throw new AssertionError("ecCSRWithData didn't return a PEMRecord");
|
||||
}
|
||||
|
||||
System.out.println("Decoding RSA pub using class PEMRecord:");
|
||||
result = PEMDecoder.of().decode(PEMData.rsapub.pem(), PEMRecord.class);
|
||||
if (!(result instanceof PEMRecord)) {
|
||||
throw new AssertionError("pubecpem didn't return a PEMRecord");
|
||||
}
|
||||
if (((PEMRecord) result).type().compareTo(Pem.PUBLIC_KEY) != 0) {
|
||||
throw new AssertionError("pubecpem PEMRecord didn't decode as a Public Key");
|
||||
}
|
||||
|
||||
testInputStream();
|
||||
testPEMRecord(PEMData.rsapub);
|
||||
testPEMRecord(PEMData.ecCert);
|
||||
testPEMRecord(PEMData.ec25519priv);
|
||||
testPEMRecord(PEMData.ecCSR);
|
||||
testPEMRecord(PEMData.ecCSRWithData);
|
||||
testPEMRecordDecode(PEMData.rsapub);
|
||||
testPEMRecordDecode(PEMData.ecCert);
|
||||
testPEMRecordDecode(PEMData.ec25519priv);
|
||||
testPEMRecordDecode(PEMData.ecCSR);
|
||||
testPEMRecordDecode(PEMData.ecCSRWithData);
|
||||
|
||||
d = PEMDecoder.of();
|
||||
System.out.println("Check leadingData is null with back-to-back PEMs: ");
|
||||
String s = new PEMRecord("ONE", "1212").toString()
|
||||
+ new PEMRecord("TWO", "3434").toString();
|
||||
var ins = new ByteArrayInputStream(s.getBytes(StandardCharsets.UTF_8));
|
||||
if (d.decode(ins, PEMRecord.class).leadingData() != null) {
|
||||
throw new AssertionError("leading data not null on first pem");
|
||||
}
|
||||
if (d.decode(ins, PEMRecord.class).leadingData() != null) {
|
||||
throw new AssertionError("leading data not null on second pem");
|
||||
}
|
||||
System.out.println("PASS");
|
||||
|
||||
System.out.println("Decode to EncryptedPrivateKeyInfo: ");
|
||||
EncryptedPrivateKeyInfo ekpi =
|
||||
d.decode(PEMData.ed25519ep8.pem(), EncryptedPrivateKeyInfo.class);
|
||||
PrivateKey privateKey;
|
||||
try {
|
||||
privateKey = ekpi.getKey(PEMData.ed25519ep8.password());
|
||||
System.out.println("PASS");
|
||||
} catch (GeneralSecurityException e) {
|
||||
throw new AssertionError("ed25519ep8 error", e);
|
||||
}
|
||||
|
||||
// PBE
|
||||
System.out.println("EncryptedPrivateKeyInfo.encryptKey with PBE: ");
|
||||
ekpi = EncryptedPrivateKeyInfo.encryptKey(privateKey,
|
||||
"password".toCharArray(),"PBEWithMD5AndDES", null, null);
|
||||
try {
|
||||
ekpi.getKey("password".toCharArray());
|
||||
System.out.println("PASS");
|
||||
} catch (Exception e) {
|
||||
throw new AssertionError("error getting key", e);
|
||||
}
|
||||
|
||||
// PBES2
|
||||
System.out.println("EncryptedPrivateKeyInfo.encryptKey with default: ");
|
||||
ekpi = EncryptedPrivateKeyInfo.encryptKey(privateKey
|
||||
, "password".toCharArray());
|
||||
try {
|
||||
ekpi.getKey("password".toCharArray());
|
||||
System.out.println("PASS");
|
||||
} catch (Exception e) {
|
||||
throw new AssertionError("error getting key", e);
|
||||
}
|
||||
}
|
||||
|
||||
static void testInputStream() throws IOException {
|
||||
ByteArrayOutputStream ba = new ByteArrayOutputStream(2048);
|
||||
OutputStreamWriter os = new OutputStreamWriter(ba);
|
||||
os.write(PEMData.preData);
|
||||
os.write(PEMData.rsapub.pem());
|
||||
os.write(PEMData.preData);
|
||||
os.write(PEMData.rsapub.pem());
|
||||
os.write(PEMData.postData);
|
||||
os.flush();
|
||||
ByteArrayInputStream is = new ByteArrayInputStream(ba.toByteArray());
|
||||
|
||||
System.out.println("Decoding 2 RSA pub with pre & post data:");
|
||||
PEMRecord obj;
|
||||
int keys = 0;
|
||||
while (keys++ < 2) {
|
||||
obj = PEMDecoder.of().decode(is, PEMRecord.class);
|
||||
if (!PEMData.preData.equalsIgnoreCase(
|
||||
new String(obj.leadingData()))) {
|
||||
System.out.println("expected: \"" + PEMData.preData + "\"");
|
||||
System.out.println("returned: \"" +
|
||||
new String(obj.leadingData()) + "\"");
|
||||
throw new AssertionError("Leading data incorrect");
|
||||
}
|
||||
System.out.println(" Read public key.");
|
||||
}
|
||||
try {
|
||||
PEMDecoder.of().decode(is, PEMRecord.class);
|
||||
throw new AssertionError("3rd entry returned a PEMRecord");
|
||||
} catch (EOFException e) {
|
||||
System.out.println("Success: No 3rd entry found. EOFE thrown.");
|
||||
}
|
||||
|
||||
// End of stream
|
||||
try {
|
||||
System.out.println("Failed: There should be no PEMRecord: " +
|
||||
PEMDecoder.of().decode(is, PEMRecord.class));
|
||||
} catch (EOFException e) {
|
||||
System.out.println("Success");
|
||||
return;
|
||||
} catch (Exception e) {
|
||||
throw new AssertionError("Caught unexpected exception " +
|
||||
"should have been IOE EOF.");
|
||||
}
|
||||
|
||||
throw new AssertionError("Failed");
|
||||
}
|
||||
|
||||
static void testPEMRecord(PEMData.Entry entry) {
|
||||
PEMRecord r = PEMDecoder.of().decode(entry.pem(), PEMRecord.class);
|
||||
String expected = entry.pem().split("-----")[2].replace(System.lineSeparator(), "");
|
||||
try {
|
||||
PEMData.checkResults(expected, r.pem());
|
||||
} catch (AssertionError e) {
|
||||
System.err.println("expected:\n" + expected);
|
||||
System.err.println("received:\n" + r.pem());
|
||||
throw e;
|
||||
}
|
||||
|
||||
boolean result = switch(r.type()) {
|
||||
case Pem.PRIVATE_KEY ->
|
||||
PrivateKey.class.isAssignableFrom(entry.clazz());
|
||||
case Pem.PUBLIC_KEY ->
|
||||
PublicKey.class.isAssignableFrom(entry.clazz());
|
||||
case Pem.CERTIFICATE, Pem.X509_CERTIFICATE ->
|
||||
entry.clazz().isAssignableFrom(X509Certificate.class);
|
||||
case Pem.X509_CRL ->
|
||||
entry.clazz().isAssignableFrom(X509CRL.class);
|
||||
case "CERTIFICATE REQUEST" ->
|
||||
entry.clazz().isAssignableFrom(PEMRecord.class);
|
||||
default -> false;
|
||||
};
|
||||
|
||||
if (!result) {
|
||||
System.err.println("PEMRecord type is a " + r.type());
|
||||
System.err.println("Entry is a " + entry.clazz().getName());
|
||||
throw new AssertionError("PEMRecord class didn't match:" +
|
||||
entry.name());
|
||||
}
|
||||
System.out.println("Success (" + entry.name() + ")");
|
||||
}
|
||||
|
||||
|
||||
static void testPEMRecordDecode(PEMData.Entry entry) {
|
||||
PEMRecord r = PEMDecoder.of().decode(entry.pem(), PEMRecord.class);
|
||||
DEREncodable de = PEMDecoder.of().decode(r.toString());
|
||||
|
||||
boolean result = switch(r.type()) {
|
||||
case Pem.PRIVATE_KEY ->
|
||||
PrivateKey.class.isAssignableFrom(de.getClass());
|
||||
case Pem.PUBLIC_KEY ->
|
||||
PublicKey.class.isAssignableFrom(de.getClass());
|
||||
case Pem.CERTIFICATE, Pem.X509_CERTIFICATE ->
|
||||
(de instanceof X509Certificate);
|
||||
case Pem.X509_CRL -> (de instanceof X509CRL);
|
||||
case "CERTIFICATE REQUEST" -> (de instanceof PEMRecord);
|
||||
default -> false;
|
||||
};
|
||||
|
||||
if (!result) {
|
||||
System.err.println("Entry is a " + entry.clazz().getName());
|
||||
System.err.println("PEMRecord type is a " + r.type());
|
||||
System.err.println("Returned was a " + entry.clazz().getName());
|
||||
throw new AssertionError("PEMRecord class didn't match:" +
|
||||
entry.name());
|
||||
}
|
||||
System.out.println("Success (" + entry.name() + ")");
|
||||
}
|
||||
|
||||
|
||||
static void testFailure(PEMData.Entry entry) {
|
||||
testFailure(entry, entry.clazz());
|
||||
}
|
||||
|
||||
static void testFailure(PEMData.Entry entry, Class c) {
|
||||
try {
|
||||
test(entry.pem(), c, PEMDecoder.of());
|
||||
if (entry.pem().indexOf('\r') != -1) {
|
||||
System.out.println("Found a CR.");
|
||||
}
|
||||
if (entry.pem().indexOf('\n') != -1) {
|
||||
System.out.println("Found a LF");
|
||||
}
|
||||
throw new AssertionError("Failure with " +
|
||||
entry.name() + ": Not supposed to succeed.");
|
||||
} catch (NullPointerException e) {
|
||||
System.out.println("PASS (" + entry.name() + "): " + e.getClass() +
|
||||
": " + e.getMessage());
|
||||
} catch (IOException | RuntimeException e) {
|
||||
System.out.println("PASS (" + entry.name() + "): " + e.getClass() +
|
||||
": " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
static DEREncodable testEncrypted(PEMData.Entry entry) {
|
||||
PEMDecoder decoder = PEMDecoder.of();
|
||||
if (!Objects.equals(entry.clazz(), EncryptedPrivateKeyInfo.class)) {
|
||||
decoder = decoder.withDecryption(entry.password());
|
||||
}
|
||||
|
||||
try {
|
||||
return test(entry.pem(), entry.clazz(), decoder);
|
||||
} catch (Exception | AssertionError e) {
|
||||
throw new RuntimeException("Error with PEM (" + entry.name() +
|
||||
"): " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
// Change the Entry to use the given class as the expected class returned
|
||||
static DEREncodable test(PEMData.Entry entry, Class c) {
|
||||
return test(entry.newClass(c));
|
||||
}
|
||||
|
||||
// Run test with a given Entry
|
||||
static DEREncodable test(PEMData.Entry entry) {
|
||||
try {
|
||||
DEREncodable r = test(entry.pem(), entry.clazz(), PEMDecoder.of());
|
||||
System.out.println("PASS (" + entry.name() + ")");
|
||||
return r;
|
||||
} catch (Exception | AssertionError e) {
|
||||
throw new RuntimeException("Error with PEM (" + entry.name() +
|
||||
"): " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
static List getInterfaceList(Class ccc) {
|
||||
Class<?>[] interfaces = ccc.getInterfaces();
|
||||
List<Class> list = new ArrayList<>(Arrays.asList(interfaces));
|
||||
var x = ccc.getSuperclass();
|
||||
if (x != null) {
|
||||
list.add(x);
|
||||
}
|
||||
List<Class> results = new ArrayList<>(list);
|
||||
if (list.size() > 0) {
|
||||
for (Class cname : list) {
|
||||
try {
|
||||
if (cname != null &&
|
||||
cname.getName().startsWith("java.security.")) {
|
||||
results.addAll(getInterfaceList(cname));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
System.err.println("Exception with " + cname);
|
||||
}
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform the decoding test with the given decoder, on the given pem, and
|
||||
* expect the clazz to be returned.
|
||||
*/
|
||||
static DEREncodable test(String pem, Class clazz, PEMDecoder decoder)
|
||||
throws IOException {
|
||||
DEREncodable pk = decoder.decode(pem);
|
||||
|
||||
// Check that clazz matches what pk returned.
|
||||
if (pk.getClass().equals(clazz)) {
|
||||
return pk;
|
||||
}
|
||||
|
||||
// Search interfaces and inheritance to find a match with clazz
|
||||
List<Class> list = getInterfaceList(pk.getClass());
|
||||
for (Class cc : list) {
|
||||
if (cc != null && cc.equals(clazz)) {
|
||||
return pk;
|
||||
}
|
||||
}
|
||||
|
||||
throw new RuntimeException("Entry did not contain expected: " +
|
||||
clazz.getName());
|
||||
}
|
||||
|
||||
// Run the same key twice through the same decoder and make sure the
|
||||
// result is the same
|
||||
static void testTwoKeys() throws IOException {
|
||||
PublicKey p1, p2;
|
||||
PEMDecoder pd = PEMDecoder.of();
|
||||
p1 = pd.decode(PEMData.rsapub.pem(), RSAPublicKey.class);
|
||||
p2 = pd.decode(PEMData.rsapub.pem(), RSAPublicKey.class);
|
||||
if (!Arrays.equals(p1.getEncoded(), p2.getEncoded())) {
|
||||
System.err.println("These two should have matched:");
|
||||
System.err.println(hex.parseHex(new String(p1.getEncoded())));
|
||||
System.err.println(hex.parseHex(new String(p2.getEncoded())));
|
||||
throw new AssertionError("Two decoding of the same" +
|
||||
" key failed to match: ");
|
||||
}
|
||||
}
|
||||
|
||||
static void testClass(PEMData.Entry entry, Class clazz) throws IOException {
|
||||
var pk = PEMDecoder.of().decode(entry.pem(), clazz);
|
||||
}
|
||||
|
||||
static void testClass(PEMData.Entry entry, Class clazz, boolean pass)
|
||||
throws RuntimeException {
|
||||
try {
|
||||
testClass(entry, clazz);
|
||||
} catch (Exception e) {
|
||||
if (pass) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Run test with a given Entry
|
||||
static void testDERCheck(PEMData.Entry entry) {
|
||||
if (entry.name().equals("rsaOpenSSL") || // PKCS1 data
|
||||
entry.name().equals("ed25519ekpi")) {
|
||||
return;
|
||||
}
|
||||
|
||||
PKCS8EncodedKeySpec p8 = PEMDecoder.of().decode(entry.pem(),
|
||||
PKCS8EncodedKeySpec.class);
|
||||
int result = Arrays.compare(entry.der(), p8.getEncoded());
|
||||
if (result != 0) {
|
||||
System.err.println("Compare error with " + entry.name() + "(" +
|
||||
result + ")");
|
||||
System.err.println("Expected DER: " + HexFormat.of().
|
||||
formatHex(entry.der()));
|
||||
System.err.println("Returned DER: " + HexFormat.of().
|
||||
formatHex(p8.getEncoded()));
|
||||
throw new AssertionError("Failed to match " +
|
||||
"expected DER");
|
||||
}
|
||||
System.out.println("PASS (" + entry.name() + ")");
|
||||
System.out.flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* Run decoded keys through Signature to make sure they are valid keys
|
||||
*/
|
||||
static void testSignature(PEMData.Entry entry) {
|
||||
Signature s;
|
||||
byte[] data = "12345678".getBytes();
|
||||
PrivateKey privateKey;
|
||||
|
||||
DEREncodable d = PEMDecoder.of().decode(entry.pem());
|
||||
switch (d) {
|
||||
case PrivateKey p -> privateKey = p;
|
||||
case KeyPair kp -> privateKey = kp.getPrivate();
|
||||
case EncryptedPrivateKeyInfo e -> {
|
||||
System.out.println("SKIP: EncryptedPrivateKeyInfo " +
|
||||
entry.name());
|
||||
return;
|
||||
}
|
||||
default -> throw new AssertionError("Private key " +
|
||||
"should not be null");
|
||||
}
|
||||
|
||||
String algorithm = switch(privateKey.getAlgorithm()) {
|
||||
case "EC" -> "SHA256withECDSA";
|
||||
case "EdDSA" -> "EdDSA";
|
||||
case null -> {
|
||||
System.out.println("Algorithm is null " +
|
||||
entry.name());
|
||||
throw new AssertionError("PrivateKey algorithm" +
|
||||
"should not be null");
|
||||
}
|
||||
default -> "SHA256with" + privateKey.getAlgorithm();
|
||||
};
|
||||
|
||||
try {
|
||||
if (d instanceof PrivateKey) {
|
||||
s = Signature.getInstance(algorithm);
|
||||
s.initSign(privateKey);
|
||||
s.update(data);
|
||||
s.sign();
|
||||
System.out.println("PASS (Sign): " + entry.name());
|
||||
} else if (d instanceof KeyPair) {
|
||||
s = Signature.getInstance(algorithm);
|
||||
s.initSign(privateKey);
|
||||
s.update(data);
|
||||
byte[] sig = s.sign();
|
||||
s.initVerify(((KeyPair)d).getPublic());
|
||||
s.verify(sig);
|
||||
System.out.println("PASS (Sign/Verify): " + entry.name());
|
||||
} else {
|
||||
System.out.println("SKIP: " + entry.name());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
System.out.println("FAIL: " + entry.name());
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
}
|
199
test/jdk/java/security/PEM/PEMEncoderTest.java
Normal file
199
test/jdk/java/security/PEM/PEMEncoderTest.java
Normal file
@ -0,0 +1,199 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @bug 8298420
|
||||
* @summary Testing basic PEM API encoding
|
||||
* @enablePreview
|
||||
* @modules java.base/sun.security.util
|
||||
*/
|
||||
|
||||
import sun.security.util.Pem;
|
||||
|
||||
import javax.crypto.EncryptedPrivateKeyInfo;
|
||||
import javax.crypto.spec.PBEParameterSpec;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.*;
|
||||
import java.security.spec.InvalidParameterSpecException;
|
||||
import java.util.*;
|
||||
|
||||
public class PEMEncoderTest {
|
||||
|
||||
static Map<String, DEREncodable> keymap;
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
PEMEncoder encoder = PEMEncoder.of();
|
||||
|
||||
// These entries are removed
|
||||
var newEntryList = new ArrayList<>(PEMData.entryList);
|
||||
newEntryList.remove(PEMData.getEntry("rsaOpenSSL"));
|
||||
newEntryList.remove(PEMData.getEntry("ecsecp256"));
|
||||
newEntryList.remove(PEMData.getEntry("ecsecp384"));
|
||||
keymap = generateObjKeyMap(newEntryList);
|
||||
System.out.println("Same instance re-encode test:");
|
||||
keymap.keySet().stream().forEach(key -> test(key, encoder));
|
||||
System.out.println("New instance re-encode test:");
|
||||
keymap.keySet().stream().forEach(key -> test(key, PEMEncoder.of()));
|
||||
System.out.println("Same instance re-encode testToString:");
|
||||
keymap.keySet().stream().forEach(key -> testToString(key, encoder));
|
||||
System.out.println("New instance re-encode testToString:");
|
||||
keymap.keySet().stream().forEach(key -> testToString(key,
|
||||
PEMEncoder.of()));
|
||||
|
||||
keymap = generateObjKeyMap(PEMData.encryptedList);
|
||||
System.out.println("Same instance Encoder match test:");
|
||||
keymap.keySet().stream().forEach(key -> testEncryptedMatch(key, encoder));
|
||||
System.out.println("Same instance Encoder new withEnc test:");
|
||||
keymap.keySet().stream().forEach(key -> testEncrypted(key, encoder));
|
||||
System.out.println("New instance Encoder and withEnc test:");
|
||||
keymap.keySet().stream().forEach(key -> testEncrypted(key, PEMEncoder.of()));
|
||||
System.out.println("Same instance encrypted Encoder test:");
|
||||
PEMEncoder encEncoder = encoder.withEncryption("fish".toCharArray());
|
||||
keymap.keySet().stream().forEach(key -> testSameEncryptor(key, encEncoder));
|
||||
try {
|
||||
encoder.withEncryption(null);
|
||||
} catch (Exception e) {
|
||||
if (!(e instanceof NullPointerException)) {
|
||||
throw new Exception("Should have been a NullPointerException thrown");
|
||||
}
|
||||
}
|
||||
|
||||
PEMDecoder d = PEMDecoder.of();
|
||||
PEMRecord pemRecord =
|
||||
d.decode(PEMData.ed25519ep8.pem(), PEMRecord.class);
|
||||
PEMData.checkResults(PEMData.ed25519ep8, pemRecord.toString());
|
||||
}
|
||||
|
||||
static Map generateObjKeyMap(List<PEMData.Entry> list) {
|
||||
Map<String, DEREncodable> keymap = new HashMap<>();
|
||||
PEMDecoder pemd = PEMDecoder.of();
|
||||
for (PEMData.Entry entry : list) {
|
||||
try {
|
||||
if (entry.password() != null) {
|
||||
keymap.put(entry.name(), pemd.withDecryption(
|
||||
entry.password()).decode(entry.pem()));
|
||||
} else {
|
||||
keymap.put(entry.name(), pemd.decode(entry.pem(),
|
||||
entry.clazz()));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
System.err.println("Verify PEMDecoderTest passes before " +
|
||||
"debugging this test.");
|
||||
throw new AssertionError("Failed to initialize map on" +
|
||||
" entry \"" + entry.name() + "\"", e);
|
||||
}
|
||||
}
|
||||
return keymap;
|
||||
}
|
||||
|
||||
static void test(String key, PEMEncoder encoder) {
|
||||
byte[] result;
|
||||
PEMData.Entry entry = PEMData.getEntry(key);
|
||||
try {
|
||||
result = encoder.encode(keymap.get(key));
|
||||
} catch (RuntimeException e) {
|
||||
throw new AssertionError("Encoder use failure with " +
|
||||
entry.name(), e);
|
||||
}
|
||||
|
||||
PEMData.checkResults(entry, new String(result, StandardCharsets.UTF_8));
|
||||
System.out.println("PASS: " + entry.name());
|
||||
}
|
||||
|
||||
static void testToString(String key, PEMEncoder encoder) {
|
||||
String result;
|
||||
PEMData.Entry entry = PEMData.getEntry(key);
|
||||
try {
|
||||
result = encoder.encodeToString(keymap.get(key));
|
||||
} catch (RuntimeException e) {
|
||||
throw new AssertionError("Encoder use failure with " +
|
||||
entry.name(), e);
|
||||
}
|
||||
|
||||
PEMData.checkResults(entry, result);
|
||||
System.out.println("PASS: " + entry.name());
|
||||
}
|
||||
|
||||
/*
|
||||
Test cannot verify PEM was the same as known PEM because we have no
|
||||
public access to the AlgoritmID.params and PBES2Parameters.
|
||||
*/
|
||||
static void testEncrypted(String key, PEMEncoder encoder) {
|
||||
PEMData.Entry entry = PEMData.getEntry(key);
|
||||
try {
|
||||
encoder.withEncryption(
|
||||
(entry.password() != null ? entry.password() :
|
||||
"fish".toCharArray()))
|
||||
.encodeToString(keymap.get(key));
|
||||
} catch (RuntimeException e) {
|
||||
throw new AssertionError("Encrypted encoder failed with " +
|
||||
entry.name(), e);
|
||||
}
|
||||
|
||||
System.out.println("PASS: " + entry.name());
|
||||
}
|
||||
|
||||
/*
|
||||
Test cannot verify PEM was the same as known PEM because we have no
|
||||
public access to the AlgoritmID.params and PBES2Parameters.
|
||||
*/
|
||||
static void testSameEncryptor(String key, PEMEncoder encoder) {
|
||||
PEMData.Entry entry = PEMData.getEntry(key);
|
||||
try {
|
||||
encoder.encodeToString(keymap.get(key));
|
||||
} catch (RuntimeException e) {
|
||||
throw new AssertionError("Encrypted encoder failured with " +
|
||||
entry.name(), e);
|
||||
}
|
||||
|
||||
System.out.println("PASS: " + entry.name());
|
||||
}
|
||||
|
||||
static void testEncryptedMatch(String key, PEMEncoder encoder) {
|
||||
String result;
|
||||
PEMData.Entry entry = PEMData.getEntry(key);
|
||||
try {
|
||||
PrivateKey pkey = (PrivateKey) keymap.get(key);
|
||||
EncryptedPrivateKeyInfo ekpi = PEMDecoder.of().decode(entry.pem(),
|
||||
EncryptedPrivateKeyInfo.class);
|
||||
if (entry.password() != null) {
|
||||
EncryptedPrivateKeyInfo.encryptKey(pkey, entry.password(),
|
||||
Pem.DEFAULT_ALGO, ekpi.getAlgParameters().
|
||||
getParameterSpec(PBEParameterSpec.class),
|
||||
null);
|
||||
}
|
||||
result = encoder.encodeToString(ekpi);
|
||||
} catch (RuntimeException | InvalidParameterSpecException e) {
|
||||
throw new AssertionError("Encrypted encoder failure with " +
|
||||
entry.name(), e);
|
||||
}
|
||||
|
||||
PEMData.checkResults(entry, result);
|
||||
System.out.println("PASS: " + entry.name());
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @bug 8298420
|
||||
* @summary Testing encryptKey
|
||||
* @enablePreview
|
||||
*/
|
||||
|
||||
import javax.crypto.EncryptedPrivateKeyInfo;
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.spec.PBEParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.security.AlgorithmParameters;
|
||||
import java.security.PEMDecoder;
|
||||
import java.security.PrivateKey;
|
||||
import java.util.Arrays;
|
||||
|
||||
public class EncryptKey {
|
||||
|
||||
private static final String encEdECKey =
|
||||
"""
|
||||
-----BEGIN ENCRYPTED PRIVATE KEY-----
|
||||
MIGqMGYGCSqGSIb3DQEFDTBZMDgGCSqGSIb3DQEFDDArBBRyYnoNyrcqvubzch00
|
||||
jyuAb5YizgICEAACARAwDAYIKoZIhvcNAgkFADAdBglghkgBZQMEAQIEEM8BgEgO
|
||||
vdMyi46+Dw7cOjwEQLtx5ME0NOOo7vlCGm3H/4j+Tf5UXrMb1UrkPjqc8OiLbC0n
|
||||
IycFtI70ciPjgwDSjtCcPxR8fSxJPrm2yOJsRVo=
|
||||
-----END ENCRYPTED PRIVATE KEY-----
|
||||
""";
|
||||
private static final String passwdText = "fish";
|
||||
private static final char[] password = passwdText.toCharArray();
|
||||
private static final SecretKey key = new SecretKeySpec(
|
||||
passwdText.getBytes(), "PBE");
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
EncryptedPrivateKeyInfo ekpi = PEMDecoder.of().decode(encEdECKey,
|
||||
EncryptedPrivateKeyInfo.class);
|
||||
PrivateKey priKey = PEMDecoder.of().withDecryption(password).
|
||||
decode(encEdECKey, PrivateKey.class);
|
||||
AlgorithmParameters ap = ekpi.getAlgParameters();
|
||||
|
||||
// Test encryptKey(PrivateKey, char[], String, ... )
|
||||
var e = EncryptedPrivateKeyInfo.encryptKey(priKey, password,
|
||||
ekpi.getAlgName(), ap.getParameterSpec(PBEParameterSpec.class),
|
||||
null);
|
||||
if (!Arrays.equals(ekpi.getEncryptedData(), e.getEncryptedData())) {
|
||||
throw new AssertionError("encryptKey() didn't match" +
|
||||
" with expected.");
|
||||
}
|
||||
|
||||
// Test encryptKey(PrivateKey, Key, String, ...)
|
||||
e = EncryptedPrivateKeyInfo.encryptKey(priKey, key, ekpi.getAlgName(),
|
||||
ap.getParameterSpec(PBEParameterSpec.class),null, null);
|
||||
if (!Arrays.equals(ekpi.getEncryptedData(), e.getEncryptedData())) {
|
||||
throw new AssertionError("encryptKey() didn't match" +
|
||||
" with expected.");
|
||||
}
|
||||
}
|
||||
}
|
76
test/jdk/javax/crypto/EncryptedPrivateKeyInfo/GetKey.java
Normal file
76
test/jdk/javax/crypto/EncryptedPrivateKeyInfo/GetKey.java
Normal file
@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @bug 8298420
|
||||
* @summary Testing getKey
|
||||
* @enablePreview
|
||||
*/
|
||||
|
||||
import javax.crypto.EncryptedPrivateKeyInfo;
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.security.PEMDecoder;
|
||||
import java.security.PrivateKey;
|
||||
import java.util.Arrays;
|
||||
|
||||
public class GetKey {
|
||||
|
||||
private static final String encEdECKey =
|
||||
"""
|
||||
-----BEGIN ENCRYPTED PRIVATE KEY-----
|
||||
MIGqMGYGCSqGSIb3DQEFDTBZMDgGCSqGSIb3DQEFDDArBBRyYnoNyrcqvubzch00
|
||||
jyuAb5YizgICEAACARAwDAYIKoZIhvcNAgkFADAdBglghkgBZQMEAQIEEM8BgEgO
|
||||
vdMyi46+Dw7cOjwEQLtx5ME0NOOo7vlCGm3H/4j+Tf5UXrMb1UrkPjqc8OiLbC0n
|
||||
IycFtI70ciPjgwDSjtCcPxR8fSxJPrm2yOJsRVo=
|
||||
-----END ENCRYPTED PRIVATE KEY-----
|
||||
""";
|
||||
private static final String passwdText = "fish";
|
||||
private static final char[] password = passwdText.toCharArray();
|
||||
private static final SecretKey key = new SecretKeySpec(
|
||||
passwdText.getBytes(), "PBE");
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
EncryptedPrivateKeyInfo ekpi = PEMDecoder.of().decode(encEdECKey,
|
||||
EncryptedPrivateKeyInfo.class);
|
||||
PrivateKey priKey = PEMDecoder.of().withDecryption(password).
|
||||
decode(encEdECKey, PrivateKey.class);
|
||||
|
||||
// Test getKey(password)
|
||||
if (!Arrays.equals(priKey.getEncoded(),
|
||||
ekpi.getKey(password).getEncoded())) {
|
||||
throw new AssertionError("getKey(char[]) didn't "
|
||||
+ "match with expected.");
|
||||
}
|
||||
|
||||
// Test getKey(key, provider)
|
||||
if (!Arrays.equals(priKey.getEncoded(),
|
||||
ekpi.getKey(key, null).getEncoded())) {
|
||||
throw new AssertionError("getKey(key, provider) " +
|
||||
"didn't match with expected.");
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2015, 2021, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -30,22 +30,18 @@
|
||||
* java.base/sun.security.util
|
||||
* java.base/sun.security.provider
|
||||
* java.base/sun.security.x509
|
||||
* @compile -XDignore.symbol.file PKCS8Test.java
|
||||
* @run testng PKCS8Test
|
||||
* @run main PKCS8Test
|
||||
*/
|
||||
|
||||
import java.io.IOException;
|
||||
import java.math.BigInteger;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.util.Arrays;
|
||||
import java.util.HexFormat;
|
||||
|
||||
import jdk.test.lib.hexdump.ASN1Formatter;
|
||||
import jdk.test.lib.hexdump.HexPrinter;
|
||||
import org.testng.Assert;
|
||||
import org.testng.annotations.Test;
|
||||
import sun.security.pkcs.PKCS8Key;
|
||||
import sun.security.provider.DSAPrivateKey;
|
||||
import sun.security.util.DerValue;
|
||||
|
||||
public class PKCS8Test {
|
||||
|
||||
@ -62,8 +58,7 @@ public class PKCS8Test {
|
||||
"3009020102020103020104" + // p=2, q=3, g=4
|
||||
"0403020101"); // PrivateKey OCTET int x = 1
|
||||
|
||||
@Test
|
||||
public void test() throws IOException {
|
||||
public static void main(String[] args) throws Exception {
|
||||
|
||||
byte[] encodedKey = new DSAPrivateKey(
|
||||
BigInteger.valueOf(1),
|
||||
@ -71,34 +66,45 @@ public class PKCS8Test {
|
||||
BigInteger.valueOf(3),
|
||||
BigInteger.valueOf(4)).getEncoded();
|
||||
|
||||
Assert.assertTrue(Arrays.equals(encodedKey, EXPECTED),
|
||||
if (!Arrays.equals(encodedKey, EXPECTED)) {
|
||||
throw new AssertionError(
|
||||
HexPrinter.simple()
|
||||
.formatter(ASN1Formatter.formatter())
|
||||
.toString(encodedKey));
|
||||
.formatter(ASN1Formatter.formatter())
|
||||
.toString(encodedKey));
|
||||
}
|
||||
|
||||
PKCS8Key decodedKey = (PKCS8Key)PKCS8Key.parseKey(encodedKey);
|
||||
|
||||
Assert.assertEquals(decodedKey.getAlgorithm(), ALGORITHM);
|
||||
Assert.assertEquals(decodedKey.getFormat(), FORMAT);
|
||||
Assert.assertEquals(decodedKey.getAlgorithmId().toString(),
|
||||
EXPECTED_ALG_ID_CHRS);
|
||||
assert(ALGORITHM.equalsIgnoreCase(decodedKey.getAlgorithm()));
|
||||
assert(FORMAT.equalsIgnoreCase(decodedKey.getFormat()));
|
||||
assert(EXPECTED_ALG_ID_CHRS.equalsIgnoreCase(decodedKey.getAlgorithmId().toString()));
|
||||
|
||||
byte[] encodedOutput = decodedKey.getEncoded();
|
||||
Assert.assertTrue(Arrays.equals(encodedOutput, EXPECTED),
|
||||
if (!Arrays.equals(encodedOutput, EXPECTED)) {
|
||||
|
||||
throw new AssertionError(
|
||||
HexPrinter.simple()
|
||||
.formatter(ASN1Formatter.formatter())
|
||||
.toString(encodedOutput));
|
||||
.formatter(ASN1Formatter.formatter())
|
||||
.toString(encodedOutput));
|
||||
}
|
||||
|
||||
// Test additional fields
|
||||
enlarge(0, "8000"); // attributes
|
||||
enlarge(1, "810100"); // public key for v2
|
||||
enlarge(1, "8000", "810100"); // both
|
||||
|
||||
Assert.assertThrows(() -> enlarge(2)); // bad ver
|
||||
Assert.assertThrows(() -> enlarge(0, "8000", "8000")); // no dup
|
||||
Assert.assertThrows(() -> enlarge(0, "810100")); // no public in v1
|
||||
Assert.assertThrows(() -> enlarge(1, "810100", "8000")); // bad order
|
||||
Assert.assertThrows(() -> enlarge(1, "820100")); // bad tag
|
||||
// PKCSv2 testing done by PEMEncoder/PEMDecoder tests
|
||||
|
||||
assertThrows(() -> enlarge(2));
|
||||
assertThrows(() -> enlarge(0, "8000", "8000")); // no dup
|
||||
assertThrows(() -> enlarge(0, "810100")); // no public in v1
|
||||
assertThrows(() -> enlarge(1, "810100", "8000")); // bad order
|
||||
assertThrows(() -> enlarge(1, "820100")); // bad tag
|
||||
}
|
||||
|
||||
private static void assertThrows(Runnable o) {
|
||||
try {
|
||||
o.run();
|
||||
throw new AssertionError("Test failed");
|
||||
} catch (Exception e) {}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -107,7 +113,7 @@ public class PKCS8Test {
|
||||
* @param newVersion new version
|
||||
* @param fields extra fields to add, in hex
|
||||
*/
|
||||
static void enlarge(int newVersion, String... fields) throws IOException {
|
||||
static void enlarge(int newVersion, String... fields) {
|
||||
byte[] original = EXPECTED.clone();
|
||||
int length = original.length;
|
||||
for (String field : fields) { // append fields
|
||||
@ -116,9 +122,13 @@ public class PKCS8Test {
|
||||
System.arraycopy(add, 0, original, length, add.length);
|
||||
length += add.length;
|
||||
}
|
||||
Assert.assertTrue(length < 127);
|
||||
original[1] = (byte)(length - 2); // the length field inside DER
|
||||
original[4] = (byte)newVersion; // the version inside DER
|
||||
PKCS8Key.parseKey(original);
|
||||
assert (length < 127);
|
||||
original[1] = (byte) (length - 2); // the length field inside DER
|
||||
original[4] = (byte) newVersion; // the version inside DER
|
||||
try {
|
||||
PKCS8Key.parseKey(original);
|
||||
} catch (InvalidKeyException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user