diff --git a/src/java.base/share/classes/javax/crypto/EncryptedPrivateKeyInfo.java b/src/java.base/share/classes/javax/crypto/EncryptedPrivateKeyInfo.java index 1e4e769a83e..2b65608593b 100644 --- a/src/java.base/share/classes/javax/crypto/EncryptedPrivateKeyInfo.java +++ b/src/java.base/share/classes/javax/crypto/EncryptedPrivateKeyInfo.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,13 +25,18 @@ package javax.crypto; -import java.io.*; +import jdk.internal.javac.PreviewFeature; + +import sun.security.jca.JCAUtil; +import sun.security.pkcs.PKCS8Key; +import sun.security.util.*; +import sun.security.x509.AlgorithmId; + +import javax.crypto.spec.PBEKeySpec; +import java.io.IOException; import java.security.*; import java.security.spec.*; -import sun.security.x509.AlgorithmId; -import sun.security.util.DerValue; -import sun.security.util.DerInputStream; -import sun.security.util.DerOutputStream; +import java.util.Objects; /** * This class implements the {@code EncryptedPrivateKeyInfo} type @@ -55,14 +60,14 @@ * @since 1.4 */ -public class EncryptedPrivateKeyInfo { +public non-sealed class EncryptedPrivateKeyInfo implements DEREncodable { // The "encryptionAlgorithm" is stored in either the algid or // the params field. Precisely, if this object is created by // {@link #EncryptedPrivateKeyInfo(AlgorithmParameters, byte[])} // with an uninitialized AlgorithmParameters, the AlgorithmParameters // object is stored in the params field and algid is set to null. - // In all other cases, algid is non null and params is null. + // In all other cases, algid is non-null and params is null. private final AlgorithmId algid; private final AlgorithmParameters params; @@ -73,19 +78,15 @@ public class EncryptedPrivateKeyInfo { private final byte[] encoded; /** - * Constructs (i.e., parses) an {@code EncryptedPrivateKeyInfo} from - * its ASN.1 encoding. + * Constructs an {@code EncryptedPrivateKeyInfo} from a given encrypted + * PKCS#8 ASN.1 encoding. * @param encoded the ASN.1 encoding of this object. The contents of * the array are copied to protect against subsequent modification. - * @exception NullPointerException if the {@code encoded} is - * {@code null}. - * @exception IOException if error occurs when parsing the ASN.1 encoding. + * @throws NullPointerException if {@code encoded} is {@code null}. + * @throws IOException if error occurs when parsing the ASN.1 encoding. */ public EncryptedPrivateKeyInfo(byte[] encoded) throws IOException { - if (encoded == null) { - throw new NullPointerException("the encoded parameter " + - "must be non-null"); - } + Objects.requireNonNull(encoded); this.encoded = encoded.clone(); DerValue val = DerValue.wrap(this.encoded); @@ -201,7 +202,7 @@ public EncryptedPrivateKeyInfo(AlgorithmParameters algParams, tmp = null; } - // one and only one is non null + // one and only one is non-null this.algid = tmp; this.params = this.algid != null ? null : algParams; @@ -219,6 +220,17 @@ public EncryptedPrivateKeyInfo(AlgorithmParameters algParams, 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. *
Note: Standard name is returned instead of the specified one @@ -308,6 +320,237 @@ private PKCS8EncodedKeySpec getKeySpecImpl(Key decryptKey, } } + /** + * Creates and encrypts an {@code EncryptedPrivateKeyInfo} from a given + * {@code PrivateKey}. A valid password-based encryption (PBE) algorithm + * and password must be specified. + * + *
The PBE algorithm string format details can be found in the + * + * Cipher section of the Java Security Standard Algorithm Names + * Specification. + * + * @param key the {@code PrivateKey} to be encrypted + * @param password the password used in the PBE encryption. This array + * will be cloned before being used. + * @param algorithm the PBE encryption algorithm. The default algorithm + * will be used if {@code null}. However, {@code null} is + * not allowed when {@code params} is non-null. + * @param params the {@code AlgorithmParameterSpec} to be used with + * encryption. The provider default will be used if + * {@code null}. + * @param provider the {@code Provider} will be used for PBE + * {@link SecretKeyFactory} generation and {@link Cipher} + * encryption operations. The default provider list will be + * used if {@code null}. + * @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); + if (provider == null) { + return KeyFactory.getInstance(p.getAlgorithm()). + generatePrivate(p); + } + return KeyFactory.getInstance(p.getAlgorithm(), + provider).generatePrivate(p); + } + /** * Extract the enclosed PKCS8EncodedKeySpec object from the * encrypted data and return it. @@ -353,12 +596,8 @@ public PKCS8EncodedKeySpec getKeySpec(Key decryptKey) public PKCS8EncodedKeySpec getKeySpec(Key decryptKey, String providerName) throws NoSuchProviderException, NoSuchAlgorithmException, InvalidKeyException { - if (decryptKey == null) { - throw new NullPointerException("decryptKey is null"); - } - if (providerName == null) { - throw new NullPointerException("provider is null"); - } + Objects.requireNonNull(decryptKey, "decryptKey is null"); + Objects.requireNonNull(providerName, "provider is null"); Provider provider = Security.getProvider(providerName); if (provider == null) { throw new NoSuchProviderException("provider " + @@ -387,12 +626,8 @@ public PKCS8EncodedKeySpec getKeySpec(Key decryptKey, public PKCS8EncodedKeySpec getKeySpec(Key decryptKey, Provider provider) throws NoSuchAlgorithmException, InvalidKeyException { - if (decryptKey == null) { - throw new NullPointerException("decryptKey is null"); - } - if (provider == null) { - throw new NullPointerException("provider is null"); - } + Objects.requireNonNull(decryptKey, "decryptKey is null"); + Objects.requireNonNull(provider, "provider is null"); return getKeySpecImpl(decryptKey, provider); } diff --git a/src/java.base/share/classes/java/security/AsymmetricKey.java b/src/java.base/share/classes/java/security/AsymmetricKey.java index e96aeb4d84c..d37afe9bfea 100644 --- a/src/java.base/share/classes/java/security/AsymmetricKey.java +++ b/src/java.base/share/classes/java/security/AsymmetricKey.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -34,7 +34,7 @@ * * @since 22 */ -public interface AsymmetricKey extends Key { +public non-sealed interface AsymmetricKey extends Key, DEREncodable { /** * Returns the parameters associated with this key. * The parameters are optional and may be either diff --git a/src/java.base/share/classes/java/security/KeyPair.java b/src/java.base/share/classes/java/security/KeyPair.java index cc648a677dd..39c98501fea 100644 --- a/src/java.base/share/classes/java/security/KeyPair.java +++ b/src/java.base/share/classes/java/security/KeyPair.java @@ -37,7 +37,7 @@ * @since 1.1 */ -public final class KeyPair implements java.io.Serializable { +public final class KeyPair implements java.io.Serializable, DEREncodable { @java.io.Serial private static final long serialVersionUID = -7565189502268009837L; diff --git a/src/java.base/share/classes/java/security/cert/X509CRL.java b/src/java.base/share/classes/java/security/cert/X509CRL.java index 111de1daf0e..d19618f81ed 100644 --- a/src/java.base/share/classes/java/security/cert/X509CRL.java +++ b/src/java.base/share/classes/java/security/cert/X509CRL.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -107,7 +107,7 @@ * @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; diff --git a/src/java.base/share/classes/java/security/cert/X509Certificate.java b/src/java.base/share/classes/java/security/cert/X509Certificate.java index 4e579a75b1c..fe4a472dead 100644 --- a/src/java.base/share/classes/java/security/cert/X509Certificate.java +++ b/src/java.base/share/classes/java/security/cert/X509Certificate.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -107,8 +107,8 @@ * @see X509Extension */ -public abstract class X509Certificate extends Certificate -implements X509Extension { +public abstract non-sealed class X509Certificate extends Certificate + implements X509Extension, DEREncodable { @java.io.Serial private static final long serialVersionUID = -2491127588187038216L; diff --git a/src/java.base/share/classes/java/security/spec/X509EncodedKeySpec.java b/src/java.base/share/classes/java/security/spec/X509EncodedKeySpec.java index a405104cd07..e8b6e599676 100644 --- a/src/java.base/share/classes/java/security/spec/X509EncodedKeySpec.java +++ b/src/java.base/share/classes/java/security/spec/X509EncodedKeySpec.java @@ -25,6 +25,8 @@ package java.security.spec; +import java.security.DEREncodable; + /** * This class represents the ASN.1 encoding of a public key, * encoded according to the ASN.1 type {@code SubjectPublicKeyInfo}. @@ -49,8 +51,8 @@ * @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. * diff --git a/src/java.base/share/classes/java/security/spec/PKCS8EncodedKeySpec.java b/src/java.base/share/classes/java/security/spec/PKCS8EncodedKeySpec.java index af7f135d7c4..917b15e06b6 100644 --- a/src/java.base/share/classes/java/security/spec/PKCS8EncodedKeySpec.java +++ b/src/java.base/share/classes/java/security/spec/PKCS8EncodedKeySpec.java @@ -25,25 +25,35 @@ package java.security.spec; +import java.security.DEREncodable; + /** * This class represents the ASN.1 encoding of a private key, - * encoded according to the ASN.1 type {@code PrivateKeyInfo}. - * The {@code PrivateKeyInfo} syntax is defined in the PKCS#8 standard + * encoded according to the ASN.1 type {@code OneAsymmetricKey}. + * The {@code OneAsymmetricKey} syntax is defined in the PKCS#8 standard * as follows: * *
- * PrivateKeyInfo ::= SEQUENCE { + * OneAsymmetricKey ::= SEQUENCE { * version Version, * privateKeyAlgorithm PrivateKeyAlgorithmIdentifier, * privateKey PrivateKey, - * attributes [0] IMPLICIT Attributes OPTIONAL } + * attributes [0] Attributes OPTIONAL, + * ..., + * [[2: publicKey [1] PublicKey OPTIONAL ]], + * ... + * } + * + * PrivateKeyInfo ::= OneAsymmetricKey * - * Version ::= INTEGER + * Version ::= INTEGER { v1(0), v2(1) } * * PrivateKeyAlgorithmIdentifier ::= AlgorithmIdentifier * * PrivateKey ::= OCTET STRING * + * PublicKey ::= BIT STRING + * * Attributes ::= SET OF Attribute ** @@ -56,11 +66,14 @@ * @see EncodedKeySpec * @see X509EncodedKeySpec * + * @spec https://www.rfc-editor.org/info/rfc5958 + * RFC 5958: Asymmetric Key Packages + * * @since 1.2 */ -public class PKCS8EncodedKeySpec extends EncodedKeySpec { - +public non-sealed class PKCS8EncodedKeySpec extends EncodedKeySpec implements + DEREncodable { /** * Creates a new {@code PKCS8EncodedKeySpec} with the given encoded key. * diff --git a/src/java.base/share/conf/security/java.security b/src/java.base/share/conf/security/java.security index b115d479838..b750c1b82b0 100644 --- a/src/java.base/share/conf/security/java.security +++ b/src/java.base/share/conf/security/java.security @@ -1549,3 +1549,12 @@ jdk.tls.alpnCharset=ISO_8859_1 # security property value defined here. # #jdk.security.krb5.name.case.sensitive=false + +# +# Default algorithm for PEMEncoder Encrypted PKCS#8 +# +# This property defines the default password-based encryption algorithm for +# java.security.PEMEncoder when configured for encryption with the +# withEncryption method. +# +jdk.epkcs8.defaultAlgorithm=PBEWithHmacSHA256AndAES_128 \ No newline at end of file