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.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
@ -34,7 +34,7 @@ import java.security.spec.AlgorithmParameterSpec;
|
|||||||
*
|
*
|
||||||
* @since 22
|
* @since 22
|
||||||
*/
|
*/
|
||||||
public interface AsymmetricKey extends Key {
|
public non-sealed interface AsymmetricKey extends Key, DEREncodable {
|
||||||
/**
|
/**
|
||||||
* Returns the parameters associated with this key.
|
* Returns the parameters associated with this key.
|
||||||
* The parameters are optional and may be either
|
* 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
|
* @since 1.1
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public final class KeyPair implements java.io.Serializable {
|
public final class KeyPair implements java.io.Serializable, DEREncodable {
|
||||||
|
|
||||||
@java.io.Serial
|
@java.io.Serial
|
||||||
private static final long serialVersionUID = -7565189502268009837L;
|
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.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
@ -107,7 +107,7 @@ import java.util.Set;
|
|||||||
* @see X509Extension
|
* @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;
|
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.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
@ -107,8 +107,8 @@ import java.util.List;
|
|||||||
* @see X509Extension
|
* @see X509Extension
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public abstract class X509Certificate extends Certificate
|
public abstract non-sealed class X509Certificate extends Certificate
|
||||||
implements X509Extension {
|
implements X509Extension, DEREncodable {
|
||||||
|
|
||||||
@java.io.Serial
|
@java.io.Serial
|
||||||
private static final long serialVersionUID = -2491127588187038216L;
|
private static final long serialVersionUID = -2491127588187038216L;
|
||||||
|
@ -25,25 +25,35 @@
|
|||||||
|
|
||||||
package java.security.spec;
|
package java.security.spec;
|
||||||
|
|
||||||
|
import java.security.DEREncodable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class represents the ASN.1 encoding of a private key,
|
* This class represents the ASN.1 encoding of a private key,
|
||||||
* encoded according to the ASN.1 type {@code PrivateKeyInfo}.
|
* encoded according to the ASN.1 type {@code OneAsymmetricKey}.
|
||||||
* The {@code PrivateKeyInfo} syntax is defined in the PKCS#8 standard
|
* The {@code OneAsymmetricKey} syntax is defined in the PKCS#8 standard
|
||||||
* as follows:
|
* as follows:
|
||||||
*
|
*
|
||||||
* <pre>
|
* <pre>
|
||||||
* PrivateKeyInfo ::= SEQUENCE {
|
* OneAsymmetricKey ::= SEQUENCE {
|
||||||
* version Version,
|
* version Version,
|
||||||
* privateKeyAlgorithm PrivateKeyAlgorithmIdentifier,
|
* privateKeyAlgorithm PrivateKeyAlgorithmIdentifier,
|
||||||
* privateKey PrivateKey,
|
* 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
|
* PrivateKeyAlgorithmIdentifier ::= AlgorithmIdentifier
|
||||||
*
|
*
|
||||||
* PrivateKey ::= OCTET STRING
|
* PrivateKey ::= OCTET STRING
|
||||||
*
|
*
|
||||||
|
* PublicKey ::= BIT STRING
|
||||||
|
*
|
||||||
* Attributes ::= SET OF Attribute
|
* Attributes ::= SET OF Attribute
|
||||||
* </pre>
|
* </pre>
|
||||||
*
|
*
|
||||||
@ -56,11 +66,14 @@ package java.security.spec;
|
|||||||
* @see EncodedKeySpec
|
* @see EncodedKeySpec
|
||||||
* @see X509EncodedKeySpec
|
* @see X509EncodedKeySpec
|
||||||
*
|
*
|
||||||
|
* @spec https://www.rfc-editor.org/info/rfc5958
|
||||||
|
* RFC 5958: Asymmetric Key Packages
|
||||||
|
*
|
||||||
* @since 1.2
|
* @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.
|
* Creates a new {@code PKCS8EncodedKeySpec} with the given encoded key.
|
||||||
*
|
*
|
||||||
|
@ -25,6 +25,8 @@
|
|||||||
|
|
||||||
package java.security.spec;
|
package java.security.spec;
|
||||||
|
|
||||||
|
import java.security.DEREncodable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class represents the ASN.1 encoding of a public key,
|
* This class represents the ASN.1 encoding of a public key,
|
||||||
* encoded according to the ASN.1 type {@code SubjectPublicKeyInfo}.
|
* encoded according to the ASN.1 type {@code SubjectPublicKeyInfo}.
|
||||||
@ -49,8 +51,8 @@ package java.security.spec;
|
|||||||
* @since 1.2
|
* @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.
|
* 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.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
@ -25,13 +25,18 @@
|
|||||||
|
|
||||||
package javax.crypto;
|
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.*;
|
||||||
import java.security.spec.*;
|
import java.security.spec.*;
|
||||||
import sun.security.x509.AlgorithmId;
|
import java.util.Objects;
|
||||||
import sun.security.util.DerValue;
|
|
||||||
import sun.security.util.DerInputStream;
|
|
||||||
import sun.security.util.DerOutputStream;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class implements the {@code EncryptedPrivateKeyInfo} type
|
* This class implements the {@code EncryptedPrivateKeyInfo} type
|
||||||
@ -55,14 +60,14 @@ import sun.security.util.DerOutputStream;
|
|||||||
* @since 1.4
|
* @since 1.4
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public class EncryptedPrivateKeyInfo {
|
public non-sealed class EncryptedPrivateKeyInfo implements DEREncodable {
|
||||||
|
|
||||||
// The "encryptionAlgorithm" is stored in either the algid or
|
// The "encryptionAlgorithm" is stored in either the algid or
|
||||||
// the params field. Precisely, if this object is created by
|
// the params field. Precisely, if this object is created by
|
||||||
// {@link #EncryptedPrivateKeyInfo(AlgorithmParameters, byte[])}
|
// {@link #EncryptedPrivateKeyInfo(AlgorithmParameters, byte[])}
|
||||||
// with an uninitialized AlgorithmParameters, the AlgorithmParameters
|
// with an uninitialized AlgorithmParameters, the AlgorithmParameters
|
||||||
// object is stored in the params field and algid is set to null.
|
// 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 AlgorithmId algid;
|
||||||
private final AlgorithmParameters params;
|
private final AlgorithmParameters params;
|
||||||
|
|
||||||
@ -73,19 +78,15 @@ public class EncryptedPrivateKeyInfo {
|
|||||||
private final byte[] encoded;
|
private final byte[] encoded;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs (i.e., parses) an {@code EncryptedPrivateKeyInfo} from
|
* Constructs an {@code EncryptedPrivateKeyInfo} from a given encrypted
|
||||||
* its ASN.1 encoding.
|
* PKCS#8 ASN.1 encoding.
|
||||||
* @param encoded the ASN.1 encoding of this object. The contents of
|
* @param encoded the ASN.1 encoding of this object. The contents of
|
||||||
* the array are copied to protect against subsequent modification.
|
* the array are copied to protect against subsequent modification.
|
||||||
* @exception NullPointerException if the {@code encoded} is
|
* @throws NullPointerException if {@code encoded} is {@code null}.
|
||||||
* {@code null}.
|
* @throws IOException if error occurs when parsing the ASN.1 encoding.
|
||||||
* @exception IOException if error occurs when parsing the ASN.1 encoding.
|
|
||||||
*/
|
*/
|
||||||
public EncryptedPrivateKeyInfo(byte[] encoded) throws IOException {
|
public EncryptedPrivateKeyInfo(byte[] encoded) throws IOException {
|
||||||
if (encoded == null) {
|
Objects.requireNonNull(encoded);
|
||||||
throw new NullPointerException("the encoded parameter " +
|
|
||||||
"must be non-null");
|
|
||||||
}
|
|
||||||
|
|
||||||
this.encoded = encoded.clone();
|
this.encoded = encoded.clone();
|
||||||
DerValue val = DerValue.wrap(this.encoded);
|
DerValue val = DerValue.wrap(this.encoded);
|
||||||
@ -201,7 +202,7 @@ public class EncryptedPrivateKeyInfo {
|
|||||||
tmp = null;
|
tmp = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// one and only one is non null
|
// one and only one is non-null
|
||||||
this.algid = tmp;
|
this.algid = tmp;
|
||||||
this.params = this.algid != null ? null : algParams;
|
this.params = this.algid != null ? null : algParams;
|
||||||
|
|
||||||
@ -219,6 +220,17 @@ public class EncryptedPrivateKeyInfo {
|
|||||||
this.encoded = null;
|
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.
|
* Returns the encryption algorithm.
|
||||||
* <p>Note: Standard name is returned instead of the specified one
|
* <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
|
* Extract the enclosed PKCS8EncodedKeySpec object from the
|
||||||
* encrypted data and return it.
|
* encrypted data and return it.
|
||||||
@ -353,12 +601,8 @@ public class EncryptedPrivateKeyInfo {
|
|||||||
public PKCS8EncodedKeySpec getKeySpec(Key decryptKey,
|
public PKCS8EncodedKeySpec getKeySpec(Key decryptKey,
|
||||||
String providerName) throws NoSuchProviderException,
|
String providerName) throws NoSuchProviderException,
|
||||||
NoSuchAlgorithmException, InvalidKeyException {
|
NoSuchAlgorithmException, InvalidKeyException {
|
||||||
if (decryptKey == null) {
|
Objects.requireNonNull(decryptKey, "decryptKey is null");
|
||||||
throw new NullPointerException("decryptKey is null");
|
Objects.requireNonNull(providerName, "provider is null");
|
||||||
}
|
|
||||||
if (providerName == null) {
|
|
||||||
throw new NullPointerException("provider is null");
|
|
||||||
}
|
|
||||||
Provider provider = Security.getProvider(providerName);
|
Provider provider = Security.getProvider(providerName);
|
||||||
if (provider == null) {
|
if (provider == null) {
|
||||||
throw new NoSuchProviderException("provider " +
|
throw new NoSuchProviderException("provider " +
|
||||||
@ -387,12 +631,8 @@ public class EncryptedPrivateKeyInfo {
|
|||||||
public PKCS8EncodedKeySpec getKeySpec(Key decryptKey,
|
public PKCS8EncodedKeySpec getKeySpec(Key decryptKey,
|
||||||
Provider provider) throws NoSuchAlgorithmException,
|
Provider provider) throws NoSuchAlgorithmException,
|
||||||
InvalidKeyException {
|
InvalidKeyException {
|
||||||
if (decryptKey == null) {
|
Objects.requireNonNull(decryptKey, "decryptKey is null");
|
||||||
throw new NullPointerException("decryptKey is null");
|
Objects.requireNonNull(provider, "provider is null");
|
||||||
}
|
|
||||||
if (provider == null) {
|
|
||||||
throw new NullPointerException("provider is null");
|
|
||||||
}
|
|
||||||
return getKeySpecImpl(decryptKey, provider);
|
return getKeySpecImpl(decryptKey, provider);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -438,23 +678,9 @@ public class EncryptedPrivateKeyInfo {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("fallthrough")
|
|
||||||
private static PKCS8EncodedKeySpec pkcs8EncodingToSpec(byte[] encodedKey)
|
private static PKCS8EncodedKeySpec pkcs8EncodingToSpec(byte[] encodedKey)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
DerInputStream in = new DerInputStream(encodedKey);
|
return new PKCS8EncodedKeySpec(encodedKey,
|
||||||
DerValue[] values = in.getSequence(3);
|
KeyUtil.getAlgorithm(encodedKey));
|
||||||
|
|
||||||
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");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -78,6 +78,8 @@ public @interface PreviewFeature {
|
|||||||
KEY_DERIVATION, //remove when the boot JDK is JDK 25
|
KEY_DERIVATION, //remove when the boot JDK is JDK 25
|
||||||
@JEP(number = 502, title = "Stable Values", status = "Preview")
|
@JEP(number = 502, title = "Stable Values", status = "Preview")
|
||||||
STABLE_VALUES,
|
STABLE_VALUES,
|
||||||
|
@JEP(number=470, title="PEM Encodings of Cryptographic Objects", status="Preview")
|
||||||
|
PEM_API,
|
||||||
LANGUAGE_MODEL,
|
LANGUAGE_MODEL,
|
||||||
/**
|
/**
|
||||||
* A key for testing.
|
* 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.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
@ -25,6 +25,8 @@
|
|||||||
|
|
||||||
package sun.security.ec;
|
package sun.security.ec;
|
||||||
|
|
||||||
|
import sun.security.pkcs.PKCS8Key;
|
||||||
|
|
||||||
import java.security.*;
|
import java.security.*;
|
||||||
import java.security.interfaces.*;
|
import java.security.interfaces.*;
|
||||||
import java.security.spec.*;
|
import java.security.spec.*;
|
||||||
@ -84,8 +86,7 @@ public final class ECKeyFactory extends KeyFactorySpi {
|
|||||||
* To be used by future Java ECDSA and ECDH implementations.
|
* To be used by future Java ECDSA and ECDH implementations.
|
||||||
*/
|
*/
|
||||||
public static ECKey toECKey(Key key) throws InvalidKeyException {
|
public static ECKey toECKey(Key key) throws InvalidKeyException {
|
||||||
if (key instanceof ECKey) {
|
if (key instanceof ECKey ecKey) {
|
||||||
ECKey ecKey = (ECKey)key;
|
|
||||||
checkKey(ecKey);
|
checkKey(ecKey);
|
||||||
return ecKey;
|
return ecKey;
|
||||||
} else {
|
} else {
|
||||||
@ -147,7 +148,7 @@ public final class ECKeyFactory extends KeyFactorySpi {
|
|||||||
|
|
||||||
// see JCA doc
|
// see JCA doc
|
||||||
protected PublicKey engineGeneratePublic(KeySpec keySpec)
|
protected PublicKey engineGeneratePublic(KeySpec keySpec)
|
||||||
throws InvalidKeySpecException {
|
throws InvalidKeySpecException {
|
||||||
try {
|
try {
|
||||||
return implGeneratePublic(keySpec);
|
return implGeneratePublic(keySpec);
|
||||||
} catch (InvalidKeySpecException e) {
|
} catch (InvalidKeySpecException e) {
|
||||||
@ -159,7 +160,7 @@ public final class ECKeyFactory extends KeyFactorySpi {
|
|||||||
|
|
||||||
// see JCA doc
|
// see JCA doc
|
||||||
protected PrivateKey engineGeneratePrivate(KeySpec keySpec)
|
protected PrivateKey engineGeneratePrivate(KeySpec keySpec)
|
||||||
throws InvalidKeySpecException {
|
throws InvalidKeySpecException {
|
||||||
try {
|
try {
|
||||||
return implGeneratePrivate(keySpec);
|
return implGeneratePrivate(keySpec);
|
||||||
} catch (InvalidKeySpecException e) {
|
} catch (InvalidKeySpecException e) {
|
||||||
@ -171,19 +172,13 @@ public final class ECKeyFactory extends KeyFactorySpi {
|
|||||||
|
|
||||||
// internal implementation of translateKey() for public keys. See JCA doc
|
// internal implementation of translateKey() for public keys. See JCA doc
|
||||||
private PublicKey implTranslatePublicKey(PublicKey key)
|
private PublicKey implTranslatePublicKey(PublicKey key)
|
||||||
throws InvalidKeyException {
|
throws InvalidKeyException {
|
||||||
if (key instanceof ECPublicKey) {
|
if (key instanceof ECPublicKeyImpl) {
|
||||||
if (key instanceof ECPublicKeyImpl) {
|
return key;
|
||||||
return key;
|
} else if (key instanceof ECPublicKey ecKey) {
|
||||||
}
|
return new ECPublicKeyImpl(ecKey.getW(), ecKey.getParams());
|
||||||
ECPublicKey ecKey = (ECPublicKey)key;
|
|
||||||
return new ECPublicKeyImpl(
|
|
||||||
ecKey.getW(),
|
|
||||||
ecKey.getParams()
|
|
||||||
);
|
|
||||||
} else if ("X.509".equals(key.getFormat())) {
|
} else if ("X.509".equals(key.getFormat())) {
|
||||||
byte[] encoded = key.getEncoded();
|
return new ECPublicKeyImpl(key.getEncoded());
|
||||||
return new ECPublicKeyImpl(encoded);
|
|
||||||
} else {
|
} else {
|
||||||
throw new InvalidKeyException("Public keys must be instance "
|
throw new InvalidKeyException("Public keys must be instance "
|
||||||
+ "of ECPublicKey or have X.509 encoding");
|
+ "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
|
// internal implementation of translateKey() for private keys. See JCA doc
|
||||||
private PrivateKey implTranslatePrivateKey(PrivateKey key)
|
private PrivateKey implTranslatePrivateKey(PrivateKey key)
|
||||||
throws InvalidKeyException {
|
throws InvalidKeyException {
|
||||||
if (key instanceof ECPrivateKey) {
|
if (key instanceof ECPrivateKeyImpl) {
|
||||||
if (key instanceof ECPrivateKeyImpl) {
|
return key;
|
||||||
return key;
|
} else if (key instanceof ECPrivateKey ecKey) {
|
||||||
}
|
return new ECPrivateKeyImpl(ecKey.getS(), ecKey.getParams());
|
||||||
ECPrivateKey ecKey = (ECPrivateKey)key;
|
|
||||||
return new ECPrivateKeyImpl(
|
|
||||||
ecKey.getS(),
|
|
||||||
ecKey.getParams()
|
|
||||||
);
|
|
||||||
} else if ("PKCS#8".equals(key.getFormat())) {
|
} else if ("PKCS#8".equals(key.getFormat())) {
|
||||||
byte[] encoded = key.getEncoded();
|
byte[] encoded = key.getEncoded();
|
||||||
try {
|
try {
|
||||||
@ -209,52 +199,54 @@ public final class ECKeyFactory extends KeyFactorySpi {
|
|||||||
} finally {
|
} finally {
|
||||||
Arrays.fill(encoded, (byte)0);
|
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
|
// internal implementation of generatePublic. See JCA doc
|
||||||
private PublicKey implGeneratePublic(KeySpec keySpec)
|
private PublicKey implGeneratePublic(KeySpec keySpec)
|
||||||
throws GeneralSecurityException {
|
throws GeneralSecurityException {
|
||||||
if (keySpec instanceof X509EncodedKeySpec) {
|
return switch (keySpec) {
|
||||||
X509EncodedKeySpec x509Spec = (X509EncodedKeySpec)keySpec;
|
case X509EncodedKeySpec x -> new ECPublicKeyImpl(x.getEncoded());
|
||||||
return new ECPublicKeyImpl(x509Spec.getEncoded());
|
case ECPublicKeySpec e ->
|
||||||
} else if (keySpec instanceof ECPublicKeySpec) {
|
new ECPublicKeyImpl(e.getW(), e.getParams());
|
||||||
ECPublicKeySpec ecSpec = (ECPublicKeySpec)keySpec;
|
case PKCS8EncodedKeySpec p8 -> {
|
||||||
return new ECPublicKeyImpl(
|
PKCS8Key p8key = new ECPrivateKeyImpl(p8.getEncoded());
|
||||||
ecSpec.getW(),
|
if (!p8key.hasPublicKey()) {
|
||||||
ecSpec.getParams()
|
throw new InvalidKeySpecException("No public key found.");
|
||||||
);
|
}
|
||||||
} else {
|
yield new ECPublicKeyImpl(p8key.getPubKeyEncoded());
|
||||||
throw new InvalidKeySpecException("Only ECPublicKeySpec "
|
}
|
||||||
+ "and X509EncodedKeySpec supported for EC public keys");
|
default ->
|
||||||
}
|
throw new InvalidKeySpecException(keySpec.getClass().getName() +
|
||||||
|
" not supported.");
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// internal implementation of generatePrivate. See JCA doc
|
// internal implementation of generatePrivate. See JCA doc
|
||||||
private PrivateKey implGeneratePrivate(KeySpec keySpec)
|
private PrivateKey implGeneratePrivate(KeySpec keySpec)
|
||||||
throws GeneralSecurityException {
|
throws GeneralSecurityException {
|
||||||
if (keySpec instanceof PKCS8EncodedKeySpec) {
|
return switch (keySpec) {
|
||||||
PKCS8EncodedKeySpec pkcsSpec = (PKCS8EncodedKeySpec)keySpec;
|
case PKCS8EncodedKeySpec p8 -> {
|
||||||
byte[] encoded = pkcsSpec.getEncoded();
|
byte[] encoded = p8.getEncoded();
|
||||||
try {
|
try {
|
||||||
return new ECPrivateKeyImpl(encoded);
|
yield new ECPrivateKeyImpl(encoded);
|
||||||
} finally {
|
} finally {
|
||||||
Arrays.fill(encoded, (byte) 0);
|
Arrays.fill(encoded, (byte) 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if (keySpec instanceof ECPrivateKeySpec) {
|
case ECPrivateKeySpec e ->
|
||||||
ECPrivateKeySpec ecSpec = (ECPrivateKeySpec)keySpec;
|
new ECPrivateKeyImpl(e.getS(), e.getParams());
|
||||||
return new ECPrivateKeyImpl(ecSpec.getS(), ecSpec.getParams());
|
default ->
|
||||||
} else {
|
throw new InvalidKeySpecException(keySpec.getClass().getName() +
|
||||||
throw new InvalidKeySpecException("Only ECPrivateKeySpec "
|
" not supported.");
|
||||||
+ "and PKCS8EncodedKeySpec supported for EC private keys");
|
};
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected <T extends KeySpec> T engineGetKeySpec(Key key, Class<T> keySpec)
|
protected <T extends KeySpec> T engineGetKeySpec(Key key, Class<T> keySpec)
|
||||||
throws InvalidKeySpecException {
|
throws InvalidKeySpecException {
|
||||||
try {
|
try {
|
||||||
// convert key to one of our keys
|
// convert key to one of our keys
|
||||||
// this also verifies that the key is a valid EC key and ensures
|
// 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) {
|
} catch (InvalidKeyException e) {
|
||||||
throw new InvalidKeySpecException(e);
|
throw new InvalidKeySpecException(e);
|
||||||
}
|
}
|
||||||
if (key instanceof ECPublicKey) {
|
if (key instanceof ECPublicKey ecKey) {
|
||||||
ECPublicKey ecKey = (ECPublicKey)key;
|
|
||||||
if (keySpec.isAssignableFrom(ECPublicKeySpec.class)) {
|
if (keySpec.isAssignableFrom(ECPublicKeySpec.class)) {
|
||||||
return keySpec.cast(new ECPublicKeySpec(
|
return keySpec.cast(new ECPublicKeySpec(
|
||||||
ecKey.getW(),
|
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.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* 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.util.*;
|
||||||
import sun.security.x509.AlgorithmId;
|
import sun.security.x509.AlgorithmId;
|
||||||
import sun.security.pkcs.PKCS8Key;
|
import sun.security.pkcs.PKCS8Key;
|
||||||
|
import sun.security.x509.X509Key;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Key implementation for EC private keys.
|
* 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
|
private byte[] arrayS; // private value as a little-endian array
|
||||||
@SuppressWarnings("serial") // Type of field is not Serializable
|
@SuppressWarnings("serial") // Type of field is not Serializable
|
||||||
private ECParameterSpec params;
|
private ECParameterSpec params;
|
||||||
|
private byte[] domainParams; //Currently unsupported
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct a key from its encoding. Called by the ECKeyFactory.
|
* 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);
|
out.putOctetString(privBytes);
|
||||||
Arrays.fill(privBytes, (byte) 0);
|
Arrays.fill(privBytes, (byte) 0);
|
||||||
DerValue val = DerValue.wrap(DerValue.tag_Sequence, out);
|
DerValue val = DerValue.wrap(DerValue.tag_Sequence, out);
|
||||||
key = val.toByteArray();
|
privKeyMaterial = val.toByteArray();
|
||||||
val.clear();
|
val.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -133,7 +135,7 @@ public final class ECPrivateKeyImpl extends PKCS8Key implements ECPrivateKey {
|
|||||||
out.putOctetString(sOctets);
|
out.putOctetString(sOctets);
|
||||||
Arrays.fill(sOctets, (byte) 0);
|
Arrays.fill(sOctets, (byte) 0);
|
||||||
DerValue val = DerValue.wrap(DerValue.tag_Sequence, out);
|
DerValue val = DerValue.wrap(DerValue.tag_Sequence, out);
|
||||||
key = val.toByteArray();
|
privKeyMaterial = val.toByteArray();
|
||||||
val.clear();
|
val.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -153,64 +155,78 @@ public final class ECPrivateKeyImpl extends PKCS8Key implements ECPrivateKey {
|
|||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] getArrayS0() {
|
// Return the internal arrayS byte[], if arrayS is null generate it.
|
||||||
|
public byte[] getArrayS() {
|
||||||
if (arrayS == null) {
|
if (arrayS == null) {
|
||||||
arrayS = ECUtil.sArray(getS(), params);
|
arrayS = ECUtil.sArray(getS(), params);
|
||||||
}
|
}
|
||||||
return arrayS;
|
return arrayS;
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] getArrayS() {
|
|
||||||
return getArrayS0().clone();
|
|
||||||
}
|
|
||||||
|
|
||||||
// see JCA doc
|
// see JCA doc
|
||||||
public ECParameterSpec getParams() {
|
public ECParameterSpec getParams() {
|
||||||
return params;
|
return params;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse the ASN.1 of the privateKey Octet
|
||||||
|
*/
|
||||||
private void parseKeyBits() throws InvalidKeyException {
|
private void parseKeyBits() throws InvalidKeyException {
|
||||||
|
// Parse private key material from PKCS8Key.decode()
|
||||||
try {
|
try {
|
||||||
DerInputStream in = new DerInputStream(key);
|
DerInputStream in = new DerInputStream(privKeyMaterial);
|
||||||
DerValue derValue = in.getDerValue();
|
DerValue derValue = in.getDerValue();
|
||||||
if (derValue.tag != DerValue.tag_Sequence) {
|
if (derValue.tag != DerValue.tag_Sequence) {
|
||||||
throw new IOException("Not a SEQUENCE");
|
throw new IOException("Not a SEQUENCE");
|
||||||
}
|
}
|
||||||
DerInputStream data = derValue.data;
|
DerInputStream data = derValue.data;
|
||||||
int version = data.getInteger();
|
int version = data.getInteger();
|
||||||
if (version != 1) {
|
if (version != V2) {
|
||||||
throw new IOException("Version must be 1");
|
throw new IOException("Version must be 1");
|
||||||
}
|
}
|
||||||
byte[] privData = data.getOctetString();
|
byte[] privData = data.getOctetString();
|
||||||
ArrayUtil.reverse(privData);
|
ArrayUtil.reverse(privData);
|
||||||
arrayS = privData;
|
arrayS = privData;
|
||||||
while (data.available() != 0) {
|
|
||||||
DerValue value = data.getDerValue();
|
// Validate parameters stored from PKCS8Key.decode()
|
||||||
if (value.isContextSpecific((byte) 0)) {
|
|
||||||
// ignore for now
|
|
||||||
} else if (value.isContextSpecific((byte) 1)) {
|
|
||||||
// ignore for now
|
|
||||||
} else {
|
|
||||||
throw new InvalidKeyException("Unexpected value: " + value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
AlgorithmParameters algParams = this.algid.getParameters();
|
AlgorithmParameters algParams = this.algid.getParameters();
|
||||||
if (algParams == null) {
|
if (algParams == null) {
|
||||||
throw new InvalidKeyException("EC domain parameters must be "
|
throw new InvalidKeyException("EC domain parameters must be "
|
||||||
+ "encoded in the algorithm identifier");
|
+ "encoded in the algorithm identifier");
|
||||||
}
|
}
|
||||||
params = algParams.getParameterSpec(ECParameterSpec.class);
|
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) {
|
} catch (IOException | InvalidParameterSpecException e) {
|
||||||
throw new InvalidKeyException("Invalid EC private key", e);
|
throw new InvalidKeyException("Invalid EC private key", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public PublicKey calculatePublicKey() {
|
public PublicKey calculatePublicKey() {
|
||||||
ECParameterSpec ecParams = getParams();
|
ECParameterSpec ecParams = getParams();
|
||||||
ECOperations ops = ECOperations.forParameters(ecParams)
|
ECOperations ops = ECOperations.forParameters(ecParams)
|
||||||
.orElseThrow(ProviderException::new);
|
.orElseThrow(ProviderException::new);
|
||||||
MutablePoint pub = ops.multiply(ecParams.getGenerator(), getArrayS0());
|
MutablePoint pub = ops.multiply(ecParams.getGenerator(), getArrayS());
|
||||||
AffinePoint affPub = pub.asAffine();
|
AffinePoint affPub = pub.asAffine();
|
||||||
ECPoint w = new ECPoint(affPub.getX().asBigInteger(),
|
ECPoint w = new ECPoint(affPub.getX().asBigInteger(),
|
||||||
affPub.getY().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.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
@ -25,12 +25,9 @@
|
|||||||
|
|
||||||
package sun.security.ec;
|
package sun.security.ec;
|
||||||
|
|
||||||
import java.security.KeyFactorySpi;
|
import sun.security.pkcs.PKCS8Key;
|
||||||
import java.security.Key;
|
|
||||||
import java.security.PublicKey;
|
import java.security.*;
|
||||||
import java.security.PrivateKey;
|
|
||||||
import java.security.InvalidKeyException;
|
|
||||||
import java.security.ProviderException;
|
|
||||||
import java.security.interfaces.XECKey;
|
import java.security.interfaces.XECKey;
|
||||||
import java.security.interfaces.XECPrivateKey;
|
import java.security.interfaces.XECPrivateKey;
|
||||||
import java.security.interfaces.XECPublicKey;
|
import java.security.interfaces.XECPublicKey;
|
||||||
@ -160,9 +157,19 @@ public class XDHKeyFactory extends KeyFactorySpi {
|
|||||||
InvalidKeySpecException::new, publicKeySpec.getParams());
|
InvalidKeySpecException::new, publicKeySpec.getParams());
|
||||||
checkLockedParams(InvalidKeySpecException::new, params);
|
checkLockedParams(InvalidKeySpecException::new, params);
|
||||||
return new XDHPublicKeyImpl(params, publicKeySpec.getU());
|
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 {
|
} else {
|
||||||
throw new InvalidKeySpecException(
|
throw new InvalidKeySpecException(keySpec.getClass().getName() +
|
||||||
"Only X509EncodedKeySpec and XECPublicKeySpec are supported");
|
" 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.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* 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);
|
DerValue val = new DerValue(DerValue.tag_OctetString, k);
|
||||||
try {
|
try {
|
||||||
this.key = val.toByteArray();
|
this.privKeyMaterial = val.toByteArray();
|
||||||
} finally {
|
} finally {
|
||||||
val.clear();
|
val.clear();
|
||||||
}
|
}
|
||||||
@ -67,7 +67,7 @@ public final class XDHPrivateKeyImpl extends PKCS8Key implements XECPrivateKey {
|
|||||||
InvalidKeyException::new, algid);
|
InvalidKeyException::new, algid);
|
||||||
paramSpec = new NamedParameterSpec(params.getName());
|
paramSpec = new NamedParameterSpec(params.getName());
|
||||||
try {
|
try {
|
||||||
DerInputStream derStream = new DerInputStream(key);
|
DerInputStream derStream = new DerInputStream(privKeyMaterial);
|
||||||
k = derStream.getOctetString();
|
k = derStream.getOctetString();
|
||||||
} catch (IOException ex) {
|
} catch (IOException ex) {
|
||||||
throw new InvalidKeyException(ex);
|
throw new InvalidKeyException(ex);
|
||||||
@ -102,7 +102,6 @@ public final class XDHPrivateKeyImpl extends PKCS8Key implements XECPrivateKey {
|
|||||||
return Optional.of(getK());
|
return Optional.of(getK());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public PublicKey calculatePublicKey() {
|
public PublicKey calculatePublicKey() {
|
||||||
XECParameters params = paramSpec.getName().equalsIgnoreCase("X25519")
|
XECParameters params = paramSpec.getName().equalsIgnoreCase("X25519")
|
||||||
? XECParameters.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.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
@ -25,12 +25,9 @@
|
|||||||
|
|
||||||
package sun.security.ec.ed;
|
package sun.security.ec.ed;
|
||||||
|
|
||||||
import java.security.KeyFactorySpi;
|
import sun.security.pkcs.PKCS8Key;
|
||||||
import java.security.Key;
|
|
||||||
import java.security.PublicKey;
|
import java.security.*;
|
||||||
import java.security.PrivateKey;
|
|
||||||
import java.security.InvalidKeyException;
|
|
||||||
import java.security.ProviderException;
|
|
||||||
import java.security.interfaces.*;
|
import java.security.interfaces.*;
|
||||||
import java.security.spec.*;
|
import java.security.spec.*;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
@ -153,9 +150,15 @@ public class EdDSAKeyFactory extends KeyFactorySpi {
|
|||||||
InvalidKeySpecException::new, publicKeySpec.getParams());
|
InvalidKeySpecException::new, publicKeySpec.getParams());
|
||||||
checkLockedParams(InvalidKeySpecException::new, params);
|
checkLockedParams(InvalidKeySpecException::new, params);
|
||||||
return new EdDSAPublicKeyImpl(params, publicKeySpec.getPoint());
|
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 {
|
} else {
|
||||||
throw new InvalidKeySpecException(
|
throw new InvalidKeySpecException(keySpec.getClass().getName() +
|
||||||
"Only X509EncodedKeySpec and EdECPublicKeySpec are supported");
|
" 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.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* 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);
|
DerValue val = new DerValue(DerValue.tag_OctetString, h);
|
||||||
try {
|
try {
|
||||||
this.key = val.toByteArray();
|
privKeyMaterial = val.toByteArray();
|
||||||
} finally {
|
} finally {
|
||||||
val.clear();
|
val.clear();
|
||||||
}
|
}
|
||||||
@ -71,7 +71,7 @@ public final class EdDSAPrivateKeyImpl
|
|||||||
paramSpec = new NamedParameterSpec(params.getName());
|
paramSpec = new NamedParameterSpec(params.getName());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
DerInputStream derStream = new DerInputStream(key);
|
DerInputStream derStream = new DerInputStream(privKeyMaterial);
|
||||||
h = derStream.getOctetString();
|
h = derStream.getOctetString();
|
||||||
} catch (IOException ex) {
|
} catch (IOException ex) {
|
||||||
throw new InvalidKeyException(ex);
|
throw new InvalidKeyException(ex);
|
||||||
@ -81,8 +81,8 @@ public final class EdDSAPrivateKeyImpl
|
|||||||
|
|
||||||
void checkLength(EdDSAParameters params) throws InvalidKeyException {
|
void checkLength(EdDSAParameters params) throws InvalidKeyException {
|
||||||
|
|
||||||
if (params.getKeyLength() != this.h.length) {
|
if (params.getKeyLength() != h.length) {
|
||||||
throw new InvalidKeyException("key length is " + this.h.length +
|
throw new InvalidKeyException("key length is " + h.length +
|
||||||
", key length must be " + params.getKeyLength());
|
", 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.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* 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);
|
DerValue val = new DerValue(DerValue.tag_OctetString, rawBytes);
|
||||||
try {
|
try {
|
||||||
this.key = val.toByteArray();
|
this.privKeyMaterial = val.toByteArray();
|
||||||
} finally {
|
} finally {
|
||||||
val.clear();
|
val.clear();
|
||||||
}
|
}
|
||||||
@ -90,7 +90,7 @@ public final class NamedPKCS8Key extends PKCS8Key {
|
|||||||
if (algid.getEncodedParams() != null) {
|
if (algid.getEncodedParams() != null) {
|
||||||
throw new InvalidKeyException("algorithm identifier has params");
|
throw new InvalidKeyException("algorithm identifier has params");
|
||||||
}
|
}
|
||||||
rawBytes = new DerInputStream(key).getOctetString();
|
rawBytes = new DerInputStream(privKeyMaterial).getOctetString();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new InvalidKeyException("Cannot parse input", e);
|
throw new InvalidKeyException("Cannot parse input", e);
|
||||||
}
|
}
|
||||||
@ -129,7 +129,7 @@ public final class NamedPKCS8Key extends PKCS8Key {
|
|||||||
@Override
|
@Override
|
||||||
public void destroy() throws DestroyFailedException {
|
public void destroy() throws DestroyFailedException {
|
||||||
Arrays.fill(rawBytes, (byte)0);
|
Arrays.fill(rawBytes, (byte)0);
|
||||||
Arrays.fill(key, (byte)0);
|
Arrays.fill(privKeyMaterial, (byte)0);
|
||||||
if (encodedKey != null) {
|
if (encodedKey != null) {
|
||||||
Arrays.fill(encodedKey, (byte)0);
|
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.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
@ -25,22 +25,18 @@
|
|||||||
|
|
||||||
package sun.security.pkcs;
|
package sun.security.pkcs;
|
||||||
|
|
||||||
import java.io.*;
|
import jdk.internal.access.SharedSecrets;
|
||||||
import java.security.Key;
|
import sun.security.util.*;
|
||||||
import java.security.KeyRep;
|
import sun.security.x509.AlgorithmId;
|
||||||
import java.security.PrivateKey;
|
import sun.security.x509.X509Key;
|
||||||
import java.security.KeyFactory;
|
|
||||||
import java.security.MessageDigest;
|
import java.io.IOException;
|
||||||
import java.security.InvalidKeyException;
|
import java.io.ObjectInputStream;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.*;
|
||||||
import java.security.spec.InvalidKeySpecException;
|
import java.security.spec.InvalidKeySpecException;
|
||||||
import java.security.spec.PKCS8EncodedKeySpec;
|
import java.security.spec.PKCS8EncodedKeySpec;
|
||||||
import java.util.Arrays;
|
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
|
* 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 {
|
public class PKCS8Key implements PrivateKey, InternalPrivateKey {
|
||||||
|
|
||||||
@ -67,20 +63,29 @@ public class PKCS8Key implements PrivateKey, InternalPrivateKey {
|
|||||||
/* The algorithm information (name, parameters, etc). */
|
/* The algorithm information (name, parameters, etc). */
|
||||||
protected AlgorithmId algid;
|
protected AlgorithmId algid;
|
||||||
|
|
||||||
/* The key bytes, without the algorithm information */
|
/* The private key OctetString for the algorithm subclasses to decode */
|
||||||
protected byte[] key;
|
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;
|
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 */
|
/* The version for this key */
|
||||||
private static final int V1 = 0;
|
public static final int V1 = 0;
|
||||||
private static final int V2 = 1;
|
public static final int V2 = 1;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default constructor. Constructors in subclasses that create a new key
|
* Default constructor. Constructors in subclasses that create a new key
|
||||||
* from its components require this. These constructors must initialize
|
* from its components require this. These constructors must initialize
|
||||||
* {@link #algid} and {@link #key}.
|
* {@link #algid} and {@link #privKeyMaterial}.
|
||||||
*/
|
*/
|
||||||
protected PKCS8Key() { }
|
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.
|
* 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 {
|
try {
|
||||||
decode(new DerValue(input));
|
decode(new DerValue(input));
|
||||||
} catch (IOException e) {
|
} 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 {
|
private void decode(DerValue val) throws InvalidKeyException {
|
||||||
try {
|
try {
|
||||||
if (val.tag != DerValue.tag_Sequence) {
|
if (val.tag != DerValue.tag_Sequence) {
|
||||||
throw new InvalidKeyException("invalid key format");
|
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) {
|
if (version != V1 && version != V2) {
|
||||||
throw new InvalidKeyException("unknown version: " + version);
|
throw new InvalidKeyException("unknown version: " + version);
|
||||||
}
|
}
|
||||||
algid = AlgorithmId.parse (val.data.getDerValue ());
|
// Parse and store AlgorithmID
|
||||||
key = val.data.getOctetString();
|
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) {
|
if (val.data.available() == 0) {
|
||||||
return;
|
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) {
|
if (val.data.available() == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
next = val.data.getDerValue();
|
v = val.data.getDerValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (next.isContextSpecific((byte)1)) {
|
// OPTIONAL context tag 1 for Public Key for PKCS8 v2 only
|
||||||
if (version == V1) {
|
if (version == V2) {
|
||||||
throw new InvalidKeyException("publicKey seen in v1");
|
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) {
|
if (val.data.available() == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new InvalidKeyException("Extra bytes");
|
throw new InvalidKeyException("Extra bytes");
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new InvalidKeyException("Unable to decode key", 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.
|
* handling, that specific need can be accommodated.
|
||||||
*
|
*
|
||||||
* @param encoded the DER-encoded SubjectPublicKeyInfo value
|
* @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 {
|
try {
|
||||||
PKCS8Key rawKey = new PKCS8Key(encoded);
|
PKCS8Key rawKey = new PKCS8Key(encoded);
|
||||||
byte[] internal = rawKey.getEncodedInternal();
|
|
||||||
PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(internal);
|
PKCS8EncodedKeySpec pkcs8KeySpec =
|
||||||
|
new PKCS8EncodedKeySpec(rawKey.generateEncoding());
|
||||||
PrivateKey result = null;
|
PrivateKey result = null;
|
||||||
try {
|
try {
|
||||||
result = KeyFactory.getInstance(rawKey.algid.getName())
|
if (provider == null) {
|
||||||
|
result = KeyFactory.getInstance(rawKey.algid.getName())
|
||||||
.generatePrivate(pkcs8KeySpec);
|
.generatePrivate(pkcs8KeySpec);
|
||||||
|
} else {
|
||||||
|
result = KeyFactory.getInstance(rawKey.algid.getName(),
|
||||||
|
provider).generatePrivate(pkcs8KeySpec);
|
||||||
|
}
|
||||||
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
|
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
|
||||||
// Ignore and return raw key
|
// Ignore and return raw key
|
||||||
result = rawKey;
|
result = rawKey;
|
||||||
@ -176,8 +224,8 @@ public class PKCS8Key implements PrivateKey, InternalPrivateKey {
|
|||||||
.clearEncodedKeySpec(pkcs8KeySpec);
|
.clearEncodedKeySpec(pkcs8KeySpec);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
} catch (InvalidKeyException e) {
|
} catch (IOException e) {
|
||||||
throw new IOException("corrupt private key", e);
|
throw new InvalidKeyException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -188,10 +236,18 @@ public class PKCS8Key implements PrivateKey, InternalPrivateKey {
|
|||||||
return algid.getName();
|
return algid.getName();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public byte[] getPubKeyEncoded() {
|
||||||
|
return pubKeyEncoded;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasPublicKey() {
|
||||||
|
return (pubKeyEncoded != null);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the algorithm ID to be used with this key.
|
* Returns the algorithm ID to be used with this key.
|
||||||
*/
|
*/
|
||||||
public AlgorithmId getAlgorithmId () {
|
public AlgorithmId getAlgorithmId () {
|
||||||
return algid;
|
return algid;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -210,6 +266,25 @@ public class PKCS8Key implements PrivateKey, InternalPrivateKey {
|
|||||||
return "PKCS#8";
|
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
|
* DER-encodes this key as a byte array stored inside this object
|
||||||
* and return it.
|
* and return it.
|
||||||
@ -218,17 +293,53 @@ public class PKCS8Key implements PrivateKey, InternalPrivateKey {
|
|||||||
*/
|
*/
|
||||||
private synchronized byte[] getEncodedInternal() {
|
private synchronized byte[] getEncodedInternal() {
|
||||||
if (encodedKey == null) {
|
if (encodedKey == null) {
|
||||||
DerOutputStream tmp = new DerOutputStream();
|
try {
|
||||||
tmp.putInteger(V1);
|
encodedKey = generateEncoding();
|
||||||
algid.encode(tmp);
|
} catch (IOException e) {
|
||||||
tmp.putOctetString(key);
|
return null;
|
||||||
DerValue out = DerValue.wrap(DerValue.tag_Sequence, tmp);
|
}
|
||||||
encodedKey = out.toByteArray();
|
|
||||||
out.clear();
|
|
||||||
}
|
}
|
||||||
return encodedKey;
|
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
|
@java.io.Serial
|
||||||
protected Object writeReplace() throws java.io.ObjectStreamException {
|
protected Object writeReplace() throws java.io.ObjectStreamException {
|
||||||
return new KeyRep(KeyRep.Type.PRIVATE,
|
return new KeyRep(KeyRep.Type.PRIVATE,
|
||||||
@ -298,6 +409,6 @@ public class PKCS8Key implements PrivateKey, InternalPrivateKey {
|
|||||||
if (encodedKey != null) {
|
if (encodedKey != null) {
|
||||||
Arrays.fill(encodedKey, (byte)0);
|
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.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* 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();
|
byte[] xbytes = x.toByteArray();
|
||||||
DerValue val = new DerValue(DerValue.tag_Integer, xbytes);
|
DerValue val = new DerValue(DerValue.tag_Integer, xbytes);
|
||||||
key = val.toByteArray();
|
privKeyMaterial = val.toByteArray();
|
||||||
val.clear();
|
val.clear();
|
||||||
Arrays.fill(xbytes, (byte)0);
|
Arrays.fill(xbytes, (byte)0);
|
||||||
}
|
}
|
||||||
@ -81,7 +81,7 @@ public final class DSAPrivateKey extends PKCS8Key
|
|||||||
public DSAPrivateKey(byte[] encoded) throws InvalidKeyException {
|
public DSAPrivateKey(byte[] encoded) throws InvalidKeyException {
|
||||||
super(encoded);
|
super(encoded);
|
||||||
try {
|
try {
|
||||||
DerInputStream in = new DerInputStream(key);
|
DerInputStream in = new DerInputStream(privKeyMaterial);
|
||||||
x = in.getBigInteger();
|
x = in.getBigInteger();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new InvalidKeyException(e.getMessage(), 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.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
@ -25,7 +25,6 @@
|
|||||||
|
|
||||||
package sun.security.provider;
|
package sun.security.provider;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
import java.security.*;
|
import java.security.*;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
@ -300,8 +299,8 @@ final class KeyProtector {
|
|||||||
// which in turn parses the key material.
|
// which in turn parses the key material.
|
||||||
try {
|
try {
|
||||||
return PKCS8Key.parseKey(plainKey);
|
return PKCS8Key.parseKey(plainKey);
|
||||||
} catch (IOException ioe) {
|
} catch (InvalidKeyException e) {
|
||||||
throw new UnrecoverableKeyException(ioe.getMessage());
|
throw new UnrecoverableKeyException(e.getMessage());
|
||||||
} finally {
|
} finally {
|
||||||
Arrays.fill(plainKey, (byte)0);
|
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.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* 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.io.*;
|
||||||
|
|
||||||
|
import java.security.PEMRecord;
|
||||||
import java.security.cert.*;
|
import java.security.cert.*;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
@ -36,6 +37,7 @@ import sun.security.provider.certpath.X509CertPath;
|
|||||||
import sun.security.provider.certpath.X509CertificatePair;
|
import sun.security.provider.certpath.X509CertificatePair;
|
||||||
import sun.security.util.Cache;
|
import sun.security.util.Cache;
|
||||||
import sun.security.util.DerValue;
|
import sun.security.util.DerValue;
|
||||||
|
import sun.security.util.Pem;
|
||||||
import sun.security.x509.X509CRLImpl;
|
import sun.security.x509.X509CRLImpl;
|
||||||
import sun.security.x509.X509CertImpl;
|
import sun.security.x509.X509CertImpl;
|
||||||
|
|
||||||
@ -556,118 +558,20 @@ public class X509Factory extends CertificateFactorySpi {
|
|||||||
readBERInternal(is, bout, c);
|
readBERInternal(is, bout, c);
|
||||||
return bout.toByteArray();
|
return bout.toByteArray();
|
||||||
} else {
|
} else {
|
||||||
// Read BASE64 encoded data, might skip info at the beginning
|
try {
|
||||||
ByteArrayOutputStream data = new ByteArrayOutputStream();
|
PEMRecord rec;
|
||||||
|
try {
|
||||||
// Step 1: Read until header is found
|
rec = Pem.readPEM(is, (c == '-' ? true : false));
|
||||||
int hyphen = (c=='-') ? 1: 0; // count of consequent hyphens
|
} catch (EOFException e) {
|
||||||
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.
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if (next == '-') {
|
return Base64.getDecoder().decode(rec.pem());
|
||||||
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());
|
|
||||||
} catch (IllegalArgumentException e) {
|
} catch (IllegalArgumentException e) {
|
||||||
throw new IOException(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
|
* Read one BER data block. This method is aware of indefinite-length BER
|
||||||
* encoding and will read all the subsections in a recursive way
|
* 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.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* 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.security.spec.*;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
import sun.security.pkcs.PKCS8Key;
|
||||||
import sun.security.rsa.RSAUtil.KeyType;
|
import sun.security.rsa.RSAUtil.KeyType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -332,9 +333,15 @@ public class RSAKeyFactory extends KeyFactorySpi {
|
|||||||
} catch (ProviderException e) {
|
} catch (ProviderException e) {
|
||||||
throw new InvalidKeySpecException(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 {
|
} else {
|
||||||
throw new InvalidKeySpecException("Only RSAPublicKeySpec "
|
throw new InvalidKeySpecException(keySpec.getClass().getName() + " not supported.");
|
||||||
+ "and X509EncodedKeySpec supported for RSA public keys");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* 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 qe; // prime exponent q
|
||||||
private BigInteger coeff; // CRT coefficient
|
private BigInteger coeff; // CRT coefficient
|
||||||
|
|
||||||
|
// RSA or RSA-PSS KeyType
|
||||||
private final transient KeyType type;
|
private final transient KeyType type;
|
||||||
|
|
||||||
// Optional parameters associated with this RSA key
|
// Optional parameters associated with this RSA key
|
||||||
@ -101,7 +102,7 @@ public final class RSAPrivateCrtKeyImpl
|
|||||||
}
|
}
|
||||||
case "PKCS#1":
|
case "PKCS#1":
|
||||||
try {
|
try {
|
||||||
BigInteger[] comps = parseASN1(encoded);
|
BigInteger[] comps = parsePKCS1(encoded);
|
||||||
if ((comps[1].signum() == 0) || (comps[3].signum() == 0) ||
|
if ((comps[1].signum() == 0) || (comps[3].signum() == 0) ||
|
||||||
(comps[4].signum() == 0) || (comps[5].signum() == 0) ||
|
(comps[4].signum() == 0) || (comps[5].signum() == 0) ||
|
||||||
(comps[6].signum() == 0) || (comps[7].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[6], (byte) 0);
|
||||||
Arrays.fill(nbytes[7], (byte) 0);
|
Arrays.fill(nbytes[7], (byte) 0);
|
||||||
DerValue val = DerValue.wrap(DerValue.tag_Sequence, out);
|
DerValue val = DerValue.wrap(DerValue.tag_Sequence, out);
|
||||||
key = val.toByteArray();
|
privKeyMaterial = val.toByteArray();
|
||||||
val.clear();
|
val.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -304,7 +305,7 @@ public final class RSAPrivateCrtKeyImpl
|
|||||||
// utility method for parsing DER encoding of RSA private keys in PKCS#1
|
// 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,
|
// 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.
|
// 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);
|
DerValue derValue = new DerValue(raw);
|
||||||
try {
|
try {
|
||||||
if (derValue.tag != DerValue.tag_Sequence) {
|
if (derValue.tag != DerValue.tag_Sequence) {
|
||||||
@ -337,7 +338,7 @@ public final class RSAPrivateCrtKeyImpl
|
|||||||
|
|
||||||
private void parseKeyBits() throws InvalidKeyException {
|
private void parseKeyBits() throws InvalidKeyException {
|
||||||
try {
|
try {
|
||||||
BigInteger[] comps = parseASN1(key);
|
BigInteger[] comps = parsePKCS1(privKeyMaterial);
|
||||||
n = comps[0];
|
n = comps[0];
|
||||||
e = comps[1];
|
e = comps[1];
|
||||||
d = comps[2];
|
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.
|
* Restores the state of this object from the stream.
|
||||||
* <p>
|
* <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.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* 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);
|
||||||
out.putInteger(0);
|
out.putInteger(0);
|
||||||
DerValue val = DerValue.wrap(DerValue.tag_Sequence, out);
|
DerValue val = DerValue.wrap(DerValue.tag_Sequence, out);
|
||||||
key = val.toByteArray();
|
privKeyMaterial = val.toByteArray();
|
||||||
val.clear();
|
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.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* 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;
|
break;
|
||||||
case "PKCS#1":
|
case "PKCS#1":
|
||||||
try {
|
try {
|
||||||
BigInteger[] comps = parseASN1(encoded);
|
BigInteger[] comps = parsePKCS1(encoded);
|
||||||
key = new RSAPublicKeyImpl(type, null, comps[0], comps[1]);
|
key = new RSAPublicKeyImpl(type, null, comps[0], comps[1]);
|
||||||
} catch (IOException ioe) {
|
} catch (IOException ioe) {
|
||||||
throw new InvalidKeyException("Invalid PKCS#1 encoding", 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
|
// 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.
|
// 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);
|
DerValue derValue = new DerValue(raw);
|
||||||
if (derValue.tag != DerValue.tag_Sequence) {
|
if (derValue.tag != DerValue.tag_Sequence) {
|
||||||
throw new IOException("Not a SEQUENCE");
|
throw new IOException("Not a SEQUENCE");
|
||||||
@ -218,7 +218,7 @@ public final class RSAPublicKeyImpl extends X509Key implements RSAPublicKey {
|
|||||||
*/
|
*/
|
||||||
protected void parseKeyBits() throws InvalidKeyException {
|
protected void parseKeyBits() throws InvalidKeyException {
|
||||||
try {
|
try {
|
||||||
BigInteger[] comps = parseASN1(getKey().toByteArray());
|
BigInteger[] comps = parsePKCS1(getKey().toByteArray());
|
||||||
n = comps[0];
|
n = comps[0];
|
||||||
e = comps[1];
|
e = comps[1];
|
||||||
} catch (IOException e) {
|
} 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.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
@ -67,6 +67,7 @@ public class DerValue {
|
|||||||
|
|
||||||
/** The tag class types */
|
/** The tag class types */
|
||||||
public static final byte TAG_UNIVERSAL = (byte)0x000;
|
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_APPLICATION = (byte)0x040;
|
||||||
public static final byte TAG_CONTEXT = (byte)0x080;
|
public static final byte TAG_CONTEXT = (byte)0x080;
|
||||||
public static final byte TAG_PRIVATE = (byte)0x0c0;
|
public static final byte TAG_PRIVATE = (byte)0x0c0;
|
||||||
|
@ -41,6 +41,7 @@ import javax.security.auth.DestroyFailedException;
|
|||||||
import jdk.internal.access.SharedSecrets;
|
import jdk.internal.access.SharedSecrets;
|
||||||
|
|
||||||
import sun.security.jca.JCAUtil;
|
import sun.security.jca.JCAUtil;
|
||||||
|
import sun.security.x509.AlgorithmId;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A utility class to get key length, validate keys, etc.
|
* 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.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
@ -25,14 +25,56 @@
|
|||||||
|
|
||||||
package sun.security.util;
|
package sun.security.util;
|
||||||
|
|
||||||
import java.io.IOException;
|
import sun.security.x509.AlgorithmId;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
import java.nio.charset.StandardCharsets;
|
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.Base64;
|
||||||
|
import java.util.HexFormat;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A utility class for PEM format encoding.
|
* A utility class for PEM format encoding.
|
||||||
*/
|
*/
|
||||||
public class Pem {
|
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.
|
* Decodes a PEM-encoded block.
|
||||||
@ -40,15 +82,264 @@ public class Pem {
|
|||||||
* @param input the input string, according to RFC 1421, can only contain
|
* @param input the input string, according to RFC 1421, can only contain
|
||||||
* characters in the base-64 alphabet and whitespaces.
|
* characters in the base-64 alphabet and whitespaces.
|
||||||
* @return the decoded bytes
|
* @return the decoded bytes
|
||||||
* @throws java.io.IOException if input is invalid
|
|
||||||
*/
|
*/
|
||||||
public static byte[] decode(String input) throws IOException {
|
public static byte[] decode(String input) {
|
||||||
byte[] src = input.replaceAll("\\s+", "")
|
byte[] src = input.replaceAll("\\s+", "").
|
||||||
.getBytes(StandardCharsets.ISO_8859_1);
|
getBytes(StandardCharsets.ISO_8859_1);
|
||||||
try {
|
|
||||||
return Base64.getDecoder().decode(src);
|
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.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* 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.NoSuchAlgorithmException;
|
||||||
import java.security.spec.InvalidKeySpecException;
|
import java.security.spec.InvalidKeySpecException;
|
||||||
import java.security.spec.X509EncodedKeySpec;
|
import java.security.spec.X509EncodedKeySpec;
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
import sun.security.util.HexDumpEncoder;
|
import sun.security.util.HexDumpEncoder;
|
||||||
import sun.security.util.*;
|
import sun.security.util.*;
|
||||||
@ -83,7 +82,8 @@ public class X509Key implements PublicKey, DerEncoder {
|
|||||||
* data is stored and transmitted losslessly, but no knowledge
|
* data is stored and transmitted losslessly, but no knowledge
|
||||||
* about this particular algorithm is available.
|
* about this particular algorithm is available.
|
||||||
*/
|
*/
|
||||||
private X509Key(AlgorithmId algid, BitArray key) {
|
@SuppressWarnings("this-escape")
|
||||||
|
public X509Key(AlgorithmId algid, BitArray key) {
|
||||||
this.algid = algid;
|
this.algid = algid;
|
||||||
setKey(key);
|
setKey(key);
|
||||||
encode();
|
encode();
|
||||||
@ -100,7 +100,7 @@ public class X509Key implements PublicKey, DerEncoder {
|
|||||||
* Gets the key. The key may or may not be byte aligned.
|
* Gets the key. The key may or may not be byte aligned.
|
||||||
* @return a BitArray containing the key.
|
* @return a BitArray containing the key.
|
||||||
*/
|
*/
|
||||||
protected BitArray getKey() {
|
public BitArray getKey() {
|
||||||
return (BitArray)bitStringKey.clone();
|
return (BitArray)bitStringKey.clone();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -129,7 +129,7 @@ public class X509Key implements PublicKey, DerEncoder {
|
|||||||
algorithm = AlgorithmId.parse(in.data.getDerValue());
|
algorithm = AlgorithmId.parse(in.data.getDerValue());
|
||||||
try {
|
try {
|
||||||
subjectKey = buildX509Key(algorithm,
|
subjectKey = buildX509Key(algorithm,
|
||||||
in.data.getUnalignedBitString());
|
in.data.getUnalignedBitString());
|
||||||
|
|
||||||
} catch (InvalidKeyException e) {
|
} catch (InvalidKeyException e) {
|
||||||
throw new IOException("subject key, " + e.getMessage(), 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.
|
* @exception InvalidKeyException on invalid key encodings.
|
||||||
*/
|
*/
|
||||||
protected void parseKeyBits() throws InvalidKeyException {
|
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.
|
* 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.
|
* Encode SubjectPublicKeyInfo sequence on the DER output stream.
|
||||||
@ -260,7 +260,7 @@ public class X509Key implements PublicKey, DerEncoder {
|
|||||||
return getEncodedInternal().clone();
|
return getEncodedInternal().clone();
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] getEncodedInternal() {
|
private byte[] getEncodedInternal() {
|
||||||
byte[] encoded = encodedKey;
|
byte[] encoded = encodedKey;
|
||||||
if (encoded == null) {
|
if (encoded == null) {
|
||||||
DerOutputStream out = new DerOutputStream();
|
DerOutputStream out = new DerOutputStream();
|
||||||
@ -314,7 +314,7 @@ public class X509Key implements PublicKey, DerEncoder {
|
|||||||
* @param val a DER-encoded X.509 SubjectPublicKeyInfo value
|
* @param val a DER-encoded X.509 SubjectPublicKeyInfo value
|
||||||
* @exception InvalidKeyException on parsing errors.
|
* @exception InvalidKeyException on parsing errors.
|
||||||
*/
|
*/
|
||||||
void decode(DerValue val) throws InvalidKeyException {
|
public void decode(DerValue val) throws InvalidKeyException {
|
||||||
try {
|
try {
|
||||||
if (val.tag != DerValue.tag_Sequence)
|
if (val.tag != DerValue.tag_Sequence)
|
||||||
throw new InvalidKeyException("invalid key format");
|
throw new InvalidKeyException("invalid key format");
|
||||||
|
@ -1549,3 +1549,12 @@ jdk.tls.alpnCharset=ISO_8859_1
|
|||||||
# security property value defined here.
|
# security property value defined here.
|
||||||
#
|
#
|
||||||
#jdk.security.krb5.name.case.sensitive=false
|
#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 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.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* 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.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* 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.util
|
||||||
* java.base/sun.security.provider
|
* java.base/sun.security.provider
|
||||||
* java.base/sun.security.x509
|
* java.base/sun.security.x509
|
||||||
* @compile -XDignore.symbol.file PKCS8Test.java
|
* @run main PKCS8Test
|
||||||
* @run testng PKCS8Test
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
|
import java.security.InvalidKeyException;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.HexFormat;
|
import java.util.HexFormat;
|
||||||
|
|
||||||
import jdk.test.lib.hexdump.ASN1Formatter;
|
import jdk.test.lib.hexdump.ASN1Formatter;
|
||||||
import jdk.test.lib.hexdump.HexPrinter;
|
import jdk.test.lib.hexdump.HexPrinter;
|
||||||
import org.testng.Assert;
|
|
||||||
import org.testng.annotations.Test;
|
|
||||||
import sun.security.pkcs.PKCS8Key;
|
import sun.security.pkcs.PKCS8Key;
|
||||||
import sun.security.provider.DSAPrivateKey;
|
import sun.security.provider.DSAPrivateKey;
|
||||||
import sun.security.util.DerValue;
|
|
||||||
|
|
||||||
public class PKCS8Test {
|
public class PKCS8Test {
|
||||||
|
|
||||||
@ -62,8 +58,7 @@ public class PKCS8Test {
|
|||||||
"3009020102020103020104" + // p=2, q=3, g=4
|
"3009020102020103020104" + // p=2, q=3, g=4
|
||||||
"0403020101"); // PrivateKey OCTET int x = 1
|
"0403020101"); // PrivateKey OCTET int x = 1
|
||||||
|
|
||||||
@Test
|
public static void main(String[] args) throws Exception {
|
||||||
public void test() throws IOException {
|
|
||||||
|
|
||||||
byte[] encodedKey = new DSAPrivateKey(
|
byte[] encodedKey = new DSAPrivateKey(
|
||||||
BigInteger.valueOf(1),
|
BigInteger.valueOf(1),
|
||||||
@ -71,34 +66,45 @@ public class PKCS8Test {
|
|||||||
BigInteger.valueOf(3),
|
BigInteger.valueOf(3),
|
||||||
BigInteger.valueOf(4)).getEncoded();
|
BigInteger.valueOf(4)).getEncoded();
|
||||||
|
|
||||||
Assert.assertTrue(Arrays.equals(encodedKey, EXPECTED),
|
if (!Arrays.equals(encodedKey, EXPECTED)) {
|
||||||
|
throw new AssertionError(
|
||||||
HexPrinter.simple()
|
HexPrinter.simple()
|
||||||
.formatter(ASN1Formatter.formatter())
|
.formatter(ASN1Formatter.formatter())
|
||||||
.toString(encodedKey));
|
.toString(encodedKey));
|
||||||
|
}
|
||||||
|
|
||||||
PKCS8Key decodedKey = (PKCS8Key)PKCS8Key.parseKey(encodedKey);
|
PKCS8Key decodedKey = (PKCS8Key)PKCS8Key.parseKey(encodedKey);
|
||||||
|
|
||||||
Assert.assertEquals(decodedKey.getAlgorithm(), ALGORITHM);
|
assert(ALGORITHM.equalsIgnoreCase(decodedKey.getAlgorithm()));
|
||||||
Assert.assertEquals(decodedKey.getFormat(), FORMAT);
|
assert(FORMAT.equalsIgnoreCase(decodedKey.getFormat()));
|
||||||
Assert.assertEquals(decodedKey.getAlgorithmId().toString(),
|
assert(EXPECTED_ALG_ID_CHRS.equalsIgnoreCase(decodedKey.getAlgorithmId().toString()));
|
||||||
EXPECTED_ALG_ID_CHRS);
|
|
||||||
|
|
||||||
byte[] encodedOutput = decodedKey.getEncoded();
|
byte[] encodedOutput = decodedKey.getEncoded();
|
||||||
Assert.assertTrue(Arrays.equals(encodedOutput, EXPECTED),
|
if (!Arrays.equals(encodedOutput, EXPECTED)) {
|
||||||
|
|
||||||
|
throw new AssertionError(
|
||||||
HexPrinter.simple()
|
HexPrinter.simple()
|
||||||
.formatter(ASN1Formatter.formatter())
|
.formatter(ASN1Formatter.formatter())
|
||||||
.toString(encodedOutput));
|
.toString(encodedOutput));
|
||||||
|
}
|
||||||
|
|
||||||
// Test additional fields
|
// Test additional fields
|
||||||
enlarge(0, "8000"); // attributes
|
enlarge(0, "8000"); // attributes
|
||||||
enlarge(1, "810100"); // public key for v2
|
|
||||||
enlarge(1, "8000", "810100"); // both
|
|
||||||
|
|
||||||
Assert.assertThrows(() -> enlarge(2)); // bad ver
|
// PKCSv2 testing done by PEMEncoder/PEMDecoder tests
|
||||||
Assert.assertThrows(() -> enlarge(0, "8000", "8000")); // no dup
|
|
||||||
Assert.assertThrows(() -> enlarge(0, "810100")); // no public in v1
|
assertThrows(() -> enlarge(2));
|
||||||
Assert.assertThrows(() -> enlarge(1, "810100", "8000")); // bad order
|
assertThrows(() -> enlarge(0, "8000", "8000")); // no dup
|
||||||
Assert.assertThrows(() -> enlarge(1, "820100")); // bad tag
|
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 newVersion new version
|
||||||
* @param fields extra fields to add, in hex
|
* @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();
|
byte[] original = EXPECTED.clone();
|
||||||
int length = original.length;
|
int length = original.length;
|
||||||
for (String field : fields) { // append fields
|
for (String field : fields) { // append fields
|
||||||
@ -116,9 +122,13 @@ public class PKCS8Test {
|
|||||||
System.arraycopy(add, 0, original, length, add.length);
|
System.arraycopy(add, 0, original, length, add.length);
|
||||||
length += add.length;
|
length += add.length;
|
||||||
}
|
}
|
||||||
Assert.assertTrue(length < 127);
|
assert (length < 127);
|
||||||
original[1] = (byte)(length - 2); // the length field inside DER
|
original[1] = (byte) (length - 2); // the length field inside DER
|
||||||
original[4] = (byte)newVersion; // the version inside DER
|
original[4] = (byte) newVersion; // the version inside DER
|
||||||
PKCS8Key.parseKey(original);
|
try {
|
||||||
|
PKCS8Key.parseKey(original);
|
||||||
|
} catch (InvalidKeyException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user