diff --git a/src/java.base/share/classes/com/sun/crypto/provider/PBES2Parameters.java b/src/java.base/share/classes/com/sun/crypto/provider/PBES2Parameters.java index 64b276a1c79a3..9d33b6689d236 100644 --- a/src/java.base/share/classes/com/sun/crypto/provider/PBES2Parameters.java +++ b/src/java.base/share/classes/com/sun/crypto/provider/PBES2Parameters.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -32,6 +32,7 @@ import java.security.spec.InvalidParameterSpecException; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.PBEParameterSpec; +import sun.security.util.PBKDF2Parameters; import sun.security.util.*; /** @@ -93,7 +94,7 @@ abstract class PBES2Parameters extends AlgorithmParametersSpi { private static final ObjectIdentifier pkcs5PBKDF2_OID = - ObjectIdentifier.of(KnownOIDs.PBKDF2WithHmacSHA1); + ObjectIdentifier.of(KnownOIDs.PBKDF2); private static final ObjectIdentifier pkcs5PBES2_OID = ObjectIdentifier.of(KnownOIDs.PBES2); private static final ObjectIdentifier aes128CBC_OID = @@ -224,77 +225,32 @@ protected void engineInit(byte[] encoded) // next DerValue as the real PBES2-params. if (kdf.getTag() == DerValue.tag_ObjectId) { pBES2_params = pBES2_params.data.getDerValue(); + if (pBES2_params.tag != DerValue.tag_Sequence) { + throw new IOException("PBE parameter parsing error: " + + "not an ASN.1 SEQUENCE tag"); + } kdf = pBES2_params.data.getDerValue(); } - String kdfAlgo = parseKDF(kdf); - - if (pBES2_params.tag != DerValue.tag_Sequence) { - throw new IOException("PBE parameter parsing error: " - + "not an ASN.1 SEQUENCE tag"); - } - String cipherAlgo = parseES(pBES2_params.data.getDerValue()); - - this.pbes2AlgorithmName = "PBEWith" + kdfAlgo + "And" + cipherAlgo; - } - - private String parseKDF(DerValue keyDerivationFunc) throws IOException { - - if (!pkcs5PBKDF2_OID.equals(keyDerivationFunc.data.getOID())) { + if (!pkcs5PBKDF2_OID.equals(kdf.data.getOID())) { throw new IOException("PBE parameter parsing error: " + "expecting the object identifier for PBKDF2"); } - if (keyDerivationFunc.tag != DerValue.tag_Sequence) { + if (kdf.tag != DerValue.tag_Sequence) { throw new IOException("PBE parameter parsing error: " + "not an ASN.1 SEQUENCE tag"); } - DerValue pBKDF2_params = keyDerivationFunc.data.getDerValue(); - if (pBKDF2_params.tag != DerValue.tag_Sequence) { - throw new IOException("PBE parameter parsing error: " - + "not an ASN.1 SEQUENCE tag"); - } - DerValue specified = pBKDF2_params.data.getDerValue(); - // the 'specified' ASN.1 CHOICE for 'salt' is supported - if (specified.tag == DerValue.tag_OctetString) { - salt = specified.getOctetString(); - } else { - // the 'otherSource' ASN.1 CHOICE for 'salt' is not supported - throw new IOException("PBE parameter parsing error: " - + "not an ASN.1 OCTET STRING tag"); - } - iCount = pBKDF2_params.data.getInteger(); + DerValue pBKDF2_params = kdf.data.getDerValue(); - // keyLength INTEGER (1..MAX) OPTIONAL, - var ksDer = pBKDF2_params.data.getOptional(DerValue.tag_Integer); - if (ksDer.isPresent()) { - keysize = ksDer.get().getInteger() * 8; // keysize (in bits) - } + var kdfParams = new PBKDF2Parameters(pBKDF2_params); + String kdfAlgo = kdfParams.getPrfAlgo(); + salt = kdfParams.getSalt(); + iCount = kdfParams.getIterationCount(); + keysize = kdfParams.getKeyLength(); - // prf AlgorithmIdentifier {{PBKDF2-PRFs}} DEFAULT algid-hmacWithSHA1 - String kdfAlgo; - var prfDer = pBKDF2_params.data.getOptional(DerValue.tag_Sequence); - if (prfDer.isPresent()) { - DerValue prf = prfDer.get(); - kdfAlgo_OID = prf.data.getOID(); - KnownOIDs o = KnownOIDs.findMatch(kdfAlgo_OID.toString()); - if (o == null || (!o.stdName().equals("HmacSHA1") && - !o.stdName().equals("HmacSHA224") && - !o.stdName().equals("HmacSHA256") && - !o.stdName().equals("HmacSHA384") && - !o.stdName().equals("HmacSHA512") && - !o.stdName().equals("HmacSHA512/224") && - !o.stdName().equals("HmacSHA512/256"))) { - throw new IOException("PBE parameter parsing error: " - + "expecting the object identifier for a HmacSHA key " - + "derivation function"); - } - kdfAlgo = o.stdName(); - prf.data.getOptional(DerValue.tag_Null); - prf.data.atEnd(); - } else { - kdfAlgo = "HmacSHA1"; - } - return kdfAlgo; + String cipherAlgo = parseES(pBES2_params.data.getDerValue()); + + this.pbes2AlgorithmName = "PBEWith" + kdfAlgo + "And" + cipherAlgo; } private String parseES(DerValue encryptionScheme) throws IOException { @@ -345,26 +301,9 @@ protected byte[] engineGetEncoded() throws IOException { DerOutputStream pBES2_params = new DerOutputStream(); - DerOutputStream keyDerivationFunc = new DerOutputStream(); - keyDerivationFunc.putOID(pkcs5PBKDF2_OID); - - DerOutputStream pBKDF2_params = new DerOutputStream(); - pBKDF2_params.putOctetString(salt); // choice: 'specified OCTET STRING' - pBKDF2_params.putInteger(iCount); - - if (keysize > 0) { - pBKDF2_params.putInteger(keysize / 8); // derived key length (in octets) - } - - DerOutputStream prf = new DerOutputStream(); - // algorithm is id-hmacWith - prf.putOID(kdfAlgo_OID); - // parameters is 'NULL' - prf.putNull(); - pBKDF2_params.write(DerValue.tag_Sequence, prf); - - keyDerivationFunc.write(DerValue.tag_Sequence, pBKDF2_params); - pBES2_params.write(DerValue.tag_Sequence, keyDerivationFunc); + // keysize encoded as octets + pBES2_params.writeBytes(PBKDF2Parameters.encode(salt, iCount, + keysize/8, kdfAlgo_OID)); DerOutputStream encryptionScheme = new DerOutputStream(); // algorithm is id-aes128-CBC or id-aes256-CBC diff --git a/src/java.base/share/classes/com/sun/crypto/provider/PBKDF2KeyImpl.java b/src/java.base/share/classes/com/sun/crypto/provider/PBKDF2KeyImpl.java index 6a0ecb6d462a7..9f3e041eebc9d 100644 --- a/src/java.base/share/classes/com/sun/crypto/provider/PBKDF2KeyImpl.java +++ b/src/java.base/share/classes/com/sun/crypto/provider/PBKDF2KeyImpl.java @@ -55,7 +55,7 @@ * @author Valerie Peng * */ -final class PBKDF2KeyImpl implements javax.crypto.interfaces.PBEKey { +public final class PBKDF2KeyImpl implements javax.crypto.interfaces.PBEKey { @java.io.Serial private static final long serialVersionUID = -2234868909660948157L; diff --git a/src/java.base/share/classes/sun/security/pkcs12/MacData.java b/src/java.base/share/classes/sun/security/pkcs12/MacData.java index 9a712f28ccc58..d45b50ad7048c 100644 --- a/src/java.base/share/classes/sun/security/pkcs12/MacData.java +++ b/src/java.base/share/classes/sun/security/pkcs12/MacData.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 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,32 +25,58 @@ package sun.security.pkcs12; -import java.io.*; +import java.io.IOException; import java.security.*; +import java.security.spec.InvalidKeySpecException; +import static java.util.Locale.ENGLISH; +import javax.crypto.Mac; +import javax.crypto.SecretKey; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.PBEKeySpec; +import javax.crypto.spec.PBEParameterSpec; -import sun.security.util.DerInputStream; -import sun.security.util.DerOutputStream; -import sun.security.util.DerValue; -import sun.security.x509.AlgorithmId; import sun.security.pkcs.ParsingException; +import sun.security.util.*; +import sun.security.x509.AlgorithmId; /** - * A MacData type, as defined in PKCS#12. + * The MacData type, as defined in PKCS#12. + * + * The ASN.1 definition is as follows: + * + *
+ *
+ * MacData ::= SEQUENCE {
+ *     mac        DigestInfo,
+ *     macSalt    OCTET STRING,
+ *     iterations INTEGER DEFAULT 1
+ *      -- Note: The default is for historical reasons and its use is
+ *      -- deprecated.
+ * }
+ *
+ * DigestInfo ::= SEQUENCE {
+ *     digestAlgorithm DigestAlgorithmIdentifier,
+ *     digest OCTET STRING
+ * }
+ *
+ * 
* * @author Sharon Liu */ class MacData { - private final String digestAlgorithmName; - private AlgorithmParameters digestAlgorithmParams; + private static final Debug debug = Debug.getInstance("pkcs12"); + private final String macAlgorithm; private final byte[] digest; private final byte[] macSalt; private final int iterations; - // the ASN.1 encoded contents of this class - private byte[] encoded = null; + // The following three fields are for PBMAC1. + private final int keyLength; + private final String kdfHmac; + private final String hmac; /** * Parses a PKCS#12 MAC data. @@ -70,103 +96,276 @@ class MacData { // Parse the DigestAlgorithmIdentifier. AlgorithmId digestAlgorithmId = AlgorithmId.parse(digestInfo[0]); - this.digestAlgorithmName = digestAlgorithmId.getName(); - this.digestAlgorithmParams = digestAlgorithmId.getParameters(); + String digestAlgorithmName = digestAlgorithmId.getName(); + // Get the digest. this.digest = digestInfo[1].getOctetString(); - // Get the salt. - this.macSalt = macData[1].getOctetString(); + if (digestAlgorithmName.equals("PBMAC1")) { + PBMAC1Parameters algParams; + + algParams = new PBMAC1Parameters(digestAlgorithmId + .getEncodedParams()); + + this.iterations = algParams.getKdfParams().getIterationCount(); + this.macSalt = algParams.getKdfParams().getSalt(); + this.kdfHmac = algParams.getKdfParams().getPrfAlgo(); + this.keyLength = algParams.getKdfParams().getKeyLength(); - // Iterations is optional. The default value is 1. - if (macData.length > 2) { - this.iterations = macData[2].getInteger(); + // Implementations MUST NOT accept params that omit keyLength. + if (this.keyLength == -1) { + throw new IOException("error: missing keyLength field"); + } + this.hmac = algParams.getHmac(); + this.macAlgorithm = "pbewith" + this.kdfHmac + "and" + this.hmac; } else { - this.iterations = 1; + this.kdfHmac = null; + this.hmac = null; + this.keyLength = -1; + this.macSalt = macData[1].getOctetString(); + if (macData.length > 2) { + this.iterations = macData[2].getInteger(); + } else { + this.iterations = 1; + } + // Remove "-" from digest algorithm names + this.macAlgorithm = "hmacpbe" + + digestAlgorithmName.replace("-", ""); } } - MacData(String algName, byte[] digest, byte[] salt, int iterations) - throws NoSuchAlgorithmException - { - if (algName == null) - throw new NullPointerException("the algName parameter " + - "must be non-null"); - - AlgorithmId algid = AlgorithmId.get(algName); - this.digestAlgorithmName = algid.getName(); - this.digestAlgorithmParams = algid.getParameters(); - - if (digest == null) { - throw new NullPointerException("the digest " + - "parameter must be non-null"); - } else if (digest.length == 0) { - throw new IllegalArgumentException("the digest " + - "parameter must not be empty"); + /** + * Computes a MAC on the data. + * + * This is a two-step process: first generate a key and then use the + * key to generate the MAC. PBMAC1 and non-PBMAC1 keys use different + * key factories. PBMAC1 uses a pseudorandom function (kdfHmac) + * to generate keys while non-PBMAC1 does not. The MAC is computed + * according to the specified hmac algorithm. + * + * @param macAlgorithm the algorithm used to compute the MAC + * @param password the password used to generate the key + * @param params a PBEParameterSpec object + * @param data the data on which the MAC is computed + * @param kdfHmac the pseudorandom function used to compute the key + * for PBMAC1 + * @param hmac the algorithm used to compute the MAC + * @param keyLength the length of the key generated by the pseudorandom + * function + * + * @return the computed MAC as a byte array + * + * @exception NoSuchAlgorithmException if either kdfHmac or hmac is + * unknown to the Mac or SecretKeyFactory + */ + private static byte[] calculateMac(String macAlgorithm, char[] password, + PBEParameterSpec params, byte[] data, + String kdfHmac, String hmac, int keyLength) + throws InvalidAlgorithmParameterException, InvalidKeyException, + InvalidKeySpecException, NoSuchAlgorithmException { + SecretKeyFactory skf; + SecretKey pbeKey = null; + Mac m; + + PBEKeySpec keySpec; + + /* + * The Hmac has to be extracted from the algorithm name for + * PBMAC1 algorithms. For non-PBMAC1 macAlgorithms, the name + * and Hmac are the same. + * + * The prefix used in Algorithm names is guaranteed to be lowercase. + */ + if (macAlgorithm.startsWith("pbewith")) { + m = Mac.getInstance(hmac); + int len = keyLength == -1 ? m.getMacLength()*8 : keyLength; + skf = SecretKeyFactory.getInstance("PBKDF2With" +kdfHmac); + keySpec = new PBEKeySpec(password, params.getSalt(), + params.getIterationCount(), len); } else { - this.digest = digest.clone(); + m = Mac.getInstance(macAlgorithm); + skf = SecretKeyFactory.getInstance("PBE"); + keySpec = new PBEKeySpec(password); } - this.macSalt = salt; - this.iterations = iterations; + try { + pbeKey = skf.generateSecret(keySpec); + if (macAlgorithm.startsWith("pbewith")) { + m.init(pbeKey); + } else { + m.init(pbeKey, params); + } + m.update(data); + return m.doFinal(); + } finally { + keySpec.clearPassword(); + KeyUtil.destroySecretKeys(pbeKey); + } + } - // delay the generation of ASN.1 encoding until - // getEncoded() is called - this.encoded = null; + /** + * Verify Mac on the data. + * + * Calculate Mac on the data and compare with Mac found in input stream. + * + * @param password the password used to generate the key + * @param data the data on which the MAC is computed + * + * @exception UnrecoverableKeyException if calculated Mac and + * Mac found in input stream are different + */ + void verifyMac(char[] password, byte[] data) + throws InvalidAlgorithmParameterException, InvalidKeyException, + InvalidKeySpecException, NoSuchAlgorithmException, + UnrecoverableKeyException { + + byte[] macResult = calculateMac(this.macAlgorithm, password, + new PBEParameterSpec(this.macSalt, this.iterations), + data, this.kdfHmac, this.hmac, this.keyLength); + + if (debug != null) { + debug.println("Checking keystore integrity " + + "(" + this.macAlgorithm + " iterations: " + + this.iterations + ")"); + } + if (!MessageDigest.isEqual(this.digest, macResult)) { + throw new UnrecoverableKeyException("Failed PKCS12" + + " integrity checking"); + } } - String getDigestAlgName() { - return digestAlgorithmName; - } + /* + * Gathers parameters and generates a MAC of the data + * + * @param password the password used to generate the key + * @param data the data on which the MAC is computed + * @param macAlgorithm the algorithm used to compute the MAC + * @param macIterationCount the iteration count + * @param salt the salt + * + * @exception IOException if the MAC cannot be calculated + * + * @return the computed MAC as a byte array + */ + static byte[] generateMac(char[] passwd, byte[] data, + String macAlgorithm, int macIterationCount, byte[] salt) + throws IOException, NoSuchAlgorithmException { + final PBEParameterSpec params; + String algName; + String kdfHmac; + String hmac; + + macAlgorithm = macAlgorithm.toLowerCase(ENGLISH); + // The prefix used in Algorithm names is guaranteed to be lowercase. + if (macAlgorithm.startsWith("pbewith")) { + algName = "PBMAC1"; + kdfHmac = MacData.parseKdfHmac(macAlgorithm); + hmac = MacData.parseHmac(macAlgorithm); + if (hmac == null) { + hmac = kdfHmac; + } + } else if (macAlgorithm.startsWith("hmacpbe")) { + algName = macAlgorithm.substring(7); + kdfHmac = null; + hmac = macAlgorithm; + } else { + throw new ParsingException("unexpected algorithm '" + + macAlgorithm + "'"); + } + + params = new PBEParameterSpec(salt, macIterationCount); + + try { + byte[] macResult = calculateMac(macAlgorithm, passwd, params, data, + kdfHmac, hmac, -1); - byte[] getSalt() { - return macSalt; + DerOutputStream bytes = new DerOutputStream(); + bytes.write(encode(algName, macResult, params, kdfHmac, hmac, + macResult.length)); + return bytes.toByteArray(); + } catch (InvalidKeySpecException | InvalidKeyException | + InvalidAlgorithmParameterException e) { + throw new IOException("calculateMac failed: " + e, e); + } } - int getIterations() { - return iterations; + String getMacAlgorithm() { + return this.macAlgorithm; } - byte[] getDigest() { - return digest; + int getIterations() { + return this.iterations; } /** - * Returns the ASN.1 encoding of this object. - * @return the ASN.1 encoding. - * @exception IOException if error occurs when constructing its + * Returns the ASN.1 encoding. + * @return the ASN.1 encoding + * @exception NoSuchAlgorithmException if error occurs when constructing its * ASN.1 encoding. */ - public byte[] getEncoded() throws NoSuchAlgorithmException - { - if (this.encoded != null) - return this.encoded.clone(); + static byte[] encode(String algName, byte[] digest, PBEParameterSpec p, + String kdfHmac, String hmac, int keyLength) + throws IOException, NoSuchAlgorithmException { + + final int iterations = p.getIterationCount(); + final byte[] macSalt = p.getSalt(); - DerOutputStream out = new DerOutputStream(); DerOutputStream tmp = new DerOutputStream(); + DerOutputStream out = new DerOutputStream(); - DerOutputStream tmp2 = new DerOutputStream(); - // encode encryption algorithm - AlgorithmId algid = AlgorithmId.get(digestAlgorithmName); - algid.encode(tmp2); + if (algName.equals("PBMAC1")) { + DerOutputStream tmp1 = new DerOutputStream(); + DerOutputStream tmp2 = new DerOutputStream(); - // encode digest data - tmp2.putOctetString(digest); + // id-PBMAC1 OBJECT IDENTIFIER ::= { pkcs-5 14 } + tmp2.putOID(ObjectIdentifier.of(KnownOIDs.PBMAC1)); + tmp2.writeBytes(PBMAC1Parameters.encode(macSalt, iterations, + keyLength, kdfHmac, hmac)); - tmp.write(DerValue.tag_Sequence, tmp2); + tmp1.write(DerValue.tag_Sequence, tmp2); + tmp1.putOctetString(digest); - // encode salt - tmp.putOctetString(macSalt); + tmp.write(DerValue.tag_Sequence, tmp1); + tmp.putOctetString( + new byte[]{ 'N', 'O', 'T', ' ', 'U', 'S', 'E', 'D' }); + // Unused, but must have non-zero positive value. + tmp.putInteger(1); + } else { + final AlgorithmId digestAlgorithm = AlgorithmId.get(algName); + DerOutputStream tmp2 = new DerOutputStream(); - // encode iterations - tmp.putInteger(iterations); + tmp2.write(digestAlgorithm); + tmp2.putOctetString(digest); + // wrap into a SEQUENCE + tmp.write(DerValue.tag_Sequence, tmp2); + tmp.putOctetString(macSalt); + tmp.putInteger(iterations); + } // wrap everything into a SEQUENCE out.write(DerValue.tag_Sequence, tmp); - this.encoded = out.toByteArray(); + return out.toByteArray(); + } - return this.encoded.clone(); + private static String parseKdfHmac(String text) { + int index1 = text.indexOf("with") + 4; + int index2 = text.indexOf("and"); + if (index1 == 3) { // -1 + 4 + return null; + } else if (index2 == -1) { + return text.substring(index1); + } else { + return text.substring(index1, index2); + } } + private static String parseHmac(String text) { + int index1 = text.indexOf("and") + 3; + if (index1 == 2) { // -1 + 3 + return null; + } else { + return text.substring(index1); + } + } } diff --git a/src/java.base/share/classes/sun/security/pkcs12/PBMAC1Parameters.java b/src/java.base/share/classes/sun/security/pkcs12/PBMAC1Parameters.java new file mode 100644 index 0000000000000..2c3c6fa817172 --- /dev/null +++ b/src/java.base/share/classes/sun/security/pkcs12/PBMAC1Parameters.java @@ -0,0 +1,140 @@ +/* + * 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 sun.security.pkcs12; + +import java.io.IOException; +import java.security.NoSuchAlgorithmException; + +import sun.security.util.*; +import sun.security.x509.AlgorithmId; + +/** + * This class implements the parameter set used with password-based + * mac scheme 1 (PBMAC1), which is defined in PKCS#5 as follows: + * + *
+ * -- PBMAC1
+ *
+ * PBMAC1Algorithms ALGORITHM-IDENTIFIER ::=
+ *   { {PBMAC1-params IDENTIFIED BY id-PBMAC1}, ...}
+ *
+ * id-PBMAC1 OBJECT IDENTIFIER ::= {pkcs-5 14}
+ *
+ * PBMAC1-params ::= SEQUENCE {
+ *   keyDerivationFunc AlgorithmIdentifier {{PBMAC1-KDFs}},
+ *   messageAuthScheme AlgorithmIdentifier {{PBMAC1-MACs}} }
+ *
+ * PBMAC1-KDFs ALGORITHM-IDENTIFIER ::=
+ *   { {PBKDF2-params IDENTIFIED BY id-PBKDF2}, ... }
+ *
+ * PBMAC1-MACs ALGORITHM-IDENTIFIER ::= { ... }
+ *
+ * -- PBKDF2
+ *
+ * See sun.security.util.PBKDF2Parameters.
+ *
+ * 
+ * + * @since 26 + */ +final class PBMAC1Parameters { + + static final ObjectIdentifier pkcs5PBKDF2_OID = + ObjectIdentifier.of(KnownOIDs.PBKDF2); + + private final String hmacAlgo; + private final PBKDF2Parameters kdfParams; + + PBMAC1Parameters(byte[] encoded) throws IOException { + DerValue pBMAC1_params = new DerValue(encoded); + if (pBMAC1_params.tag != DerValue.tag_Sequence) { + throw new IOException("PBMAC1 parameter parsing error: " + + "not an ASN.1 SEQUENCE tag"); + } + DerValue[] info = new DerInputStream(pBMAC1_params.toByteArray()) + .getSequence(2); + if (info.length != 2) { + throw new IOException("PBMAC1 parameter parsing error: " + + "expected length not 2"); + } + ObjectIdentifier OID = info[1].data.getOID(); + KnownOIDs o = KnownOIDs.findMatch(OID.toString()); + if (o == null || (!o.stdName().equals("HmacSHA1") && + !o.stdName().equals("HmacSHA224") && + !o.stdName().equals("HmacSHA256") && + !o.stdName().equals("HmacSHA384") && + !o.stdName().equals("HmacSHA512") && + !o.stdName().equals("HmacSHA512/224") && + !o.stdName().equals("HmacSHA512/256"))) { + throw new IOException("PBMAC1 parameter parsing error: " + + "expecting the object identifier for a HmacSHA key " + + "derivation function"); + } + // Hmac function used to compute the MAC + this.hmacAlgo = o.stdName(); + + //DerValue kdf = pBMAC1_params.data.getDerValue(); + DerValue kdf = info[0]; + + if (!pkcs5PBKDF2_OID.equals(kdf.data.getOID())) { + throw new IOException("PBKDF2 parameter parsing error: " + + "expecting the object identifier for PBKDF2"); + } + if (kdf.tag != DerValue.tag_Sequence) { + throw new IOException("PBKDF2 parameter parsing error: " + + "not an ASN.1 SEQUENCE tag"); + } + DerValue pBKDF2_params = kdf.data.getDerValue(); + + this.kdfParams = new PBKDF2Parameters(pBKDF2_params); + } + + /* + * Encode PBMAC1 parameters from components. + */ + static byte[] encode(byte[] salt, int iterationCount, int keyLength, + String kdfHmac, String hmac) throws NoSuchAlgorithmException { + + DerOutputStream out = new DerOutputStream(); + + // keyDerivationFunc AlgorithmIdentifier {{PBMAC1-KDFs}} + out.writeBytes(PBKDF2Parameters.encode(salt, + iterationCount, keyLength, kdfHmac)); + + // messageAuthScheme AlgorithmIdentifier {{PBMAC1-MACs}} + out.write(AlgorithmId.get(hmac)); + return new DerOutputStream().write(DerValue.tag_Sequence, out) + .toByteArray(); + } + + PBKDF2Parameters getKdfParams() { + return this.kdfParams; + } + + String getHmac() { + return this.hmacAlgo; + } +} diff --git a/src/java.base/share/classes/sun/security/pkcs12/PKCS12KeyStore.java b/src/java.base/share/classes/sun/security/pkcs12/PKCS12KeyStore.java index bee898567567a..f85ba0f9c4b92 100644 --- a/src/java.base/share/classes/sun/security/pkcs12/PKCS12KeyStore.java +++ b/src/java.base/share/classes/sun/security/pkcs12/PKCS12KeyStore.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 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 @@ -26,52 +26,36 @@ package sun.security.pkcs12; import java.io.*; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.security.Key; -import java.security.KeyFactory; -import java.security.KeyStore; -import java.security.KeyStoreSpi; -import java.security.KeyStoreException; -import java.security.PKCS12Attribute; -import java.security.PrivateKey; -import java.security.UnrecoverableEntryException; -import java.security.UnrecoverableKeyException; -import java.security.SecureRandom; -import java.security.Security; +import java.security.*; import java.security.cert.Certificate; +import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; -import java.security.cert.CertificateException; import java.security.spec.AlgorithmParameterSpec; import java.security.spec.InvalidParameterSpecException; import java.security.spec.KeySpec; import java.security.spec.PKCS8EncodedKeySpec; import java.util.*; - -import static java.nio.charset.StandardCharsets.UTF_8; - -import java.security.AlgorithmParameters; -import java.security.InvalidAlgorithmParameterException; -import javax.crypto.spec.PBEParameterSpec; +import javax.crypto.Cipher; +import javax.crypto.SecretKey; +import javax.crypto.SecretKeyFactory; import javax.crypto.spec.PBEKeySpec; +import javax.crypto.spec.PBEParameterSpec; import javax.crypto.spec.SecretKeySpec; -import javax.crypto.SecretKeyFactory; -import javax.crypto.SecretKey; -import javax.crypto.Cipher; -import javax.crypto.Mac; import javax.security.auth.DestroyFailedException; import javax.security.auth.x500.X500Principal; import jdk.internal.access.SharedSecrets; -import sun.security.tools.KeyStoreUtil; -import sun.security.util.*; import sun.security.pkcs.ContentInfo; -import sun.security.x509.AlgorithmId; import sun.security.pkcs.EncryptedPrivateKeyInfo; import sun.security.provider.JavaKeyStore.JKS; +import sun.security.tools.KeyStoreUtil; +import sun.security.util.*; +import sun.security.x509.AlgorithmId; import sun.security.x509.AuthorityKeyIdentifierExtension; +import static java.nio.charset.StandardCharsets.UTF_8; + /** * This class provides the keystore implementation referred to as "PKCS12". @@ -366,7 +350,7 @@ private Key internalGetKey(Entry entry, char[] password) try { cipher.init(Cipher.DECRYPT_MODE, skey, algParams); } finally { - destroyPBEKey(skey); + KeyUtil.destroySecretKeys(skey); } byte[] keyInfo = cipher.doFinal(encryptedKey); /* @@ -855,17 +839,6 @@ private SecretKey getPBEKey(char[] password) throws IOException return skey; } - /* - * Destroy the key obtained from getPBEKey(). - */ - private void destroyPBEKey(SecretKey key) { - try { - key.destroy(); - } catch (DestroyFailedException e) { - // Accept this - } - } - /* * Encrypt private key or secret key using Password-based encryption (PBE) * as defined in PKCS#5. @@ -915,7 +888,7 @@ private byte[] encryptPrivateKey(byte[] data, try { cipher.init(Cipher.ENCRYPT_MODE, skey, algParams); } finally { - destroyPBEKey(skey); + KeyUtil.destroySecretKeys(skey); } byte[] encryptedKey = cipher.doFinal(data); algid = new AlgorithmId(pbeOID, cipher.getParameters()); @@ -1265,7 +1238,8 @@ public synchronized void engineStore(OutputStream stream, char[] password) macIterationCount = defaultMacIterationCount(); } if (password != null && !macAlgorithm.equalsIgnoreCase("NONE")) { - byte[] macData = calculateMac(password, authenticatedSafe); + byte[] macData = MacData.generateMac(password, authenticatedSafe, + macAlgorithm, macIterationCount, getSalt()); pfx.write(macData); } // write PFX to output stream @@ -1480,48 +1454,6 @@ private void populateAttributes(Entry entry) { } } - /* - * Calculate MAC using HMAC algorithm (required for password integrity) - * - * Hash-based MAC algorithm combines secret key with message digest to - * create a message authentication code (MAC) - */ - private byte[] calculateMac(char[] passwd, byte[] data) - throws IOException - { - byte[] mData; - String algName = macAlgorithm.substring(7); - - try { - // Generate a random salt. - byte[] salt = getSalt(); - - // generate MAC (MAC key is generated within JCE) - Mac m = Mac.getInstance(macAlgorithm); - PBEParameterSpec params = - new PBEParameterSpec(salt, macIterationCount); - SecretKey key = getPBEKey(passwd); - try { - m.init(key, params); - } finally { - destroyPBEKey(key); - } - m.update(data); - byte[] macResult = m.doFinal(); - - // encode as MacData - MacData macData = new MacData(algName, macResult, salt, - macIterationCount); - DerOutputStream bytes = new DerOutputStream(); - bytes.write(macData.getEncoded()); - mData = bytes.toByteArray(); - } catch (Exception e) { - throw new IOException("calculateMac failed: " + e, e); - } - return mData; - } - - /* * Validate Certificate Chain */ @@ -1890,7 +1822,7 @@ private byte[] encryptContent(byte[] data, char[] password) try { cipher.init(Cipher.ENCRYPT_MODE, skey, algParams); } finally { - destroyPBEKey(skey); + KeyUtil.destroySecretKeys(skey); } encryptedData = cipher.doFinal(data); @@ -2100,7 +2032,7 @@ public synchronized void engineLoad(InputStream stream, char[] password) try { cipher.init(Cipher.DECRYPT_MODE, skey, algParams); } finally { - destroyPBEKey(skey); + KeyUtil.destroySecretKeys(skey); } loadSafeContents(new DerInputStream(cipher.doFinal(rawData))); return null; @@ -2135,39 +2067,11 @@ public synchronized void engineLoad(InputStream stream, char[] password) "MAC iteration count too large: " + ic); } - String algName = - macData.getDigestAlgName().toUpperCase(Locale.ENGLISH); - - // Change SHA-1 to SHA1 - algName = algName.replace("-", ""); - - macAlgorithm = "HmacPBE" + algName; + // Store MAC algorithm of keystore that was just loaded. + macAlgorithm = macData.getMacAlgorithm(); macIterationCount = ic; - - // generate MAC (MAC key is created within JCE) - Mac m = Mac.getInstance(macAlgorithm); - PBEParameterSpec params = - new PBEParameterSpec(macData.getSalt(), ic); - RetryWithZero.run(pass -> { - SecretKey key = getPBEKey(pass); - try { - m.init(key, params); - } finally { - destroyPBEKey(key); - } - m.update(authSafeData); - byte[] macResult = m.doFinal(); - - if (debug != null) { - debug.println("Checking keystore integrity " + - "(" + m.getAlgorithm() + " iterations: " + ic + ")"); - } - - if (!MessageDigest.isEqual(macData.getDigest(), macResult)) { - throw new UnrecoverableKeyException("Failed PKCS12" + - " integrity checking"); - } + macData.verifyMac(pass, authSafeData); return (Void) null; }, password); } catch (Exception e) { diff --git a/src/java.base/share/classes/sun/security/util/KeyUtil.java b/src/java.base/share/classes/sun/security/util/KeyUtil.java index 7a58ac0d4e9ab..dd27b5f02d800 100644 --- a/src/java.base/share/classes/sun/security/util/KeyUtil.java +++ b/src/java.base/share/classes/sun/security/util/KeyUtil.java @@ -40,6 +40,7 @@ import javax.security.auth.DestroyFailedException; import jdk.internal.access.SharedSecrets; +import com.sun.crypto.provider.PBKDF2KeyImpl; import sun.security.jca.JCAUtil; import sun.security.x509.AlgorithmId; @@ -469,6 +470,8 @@ public static void destroySecretKeys(SecretKey... keys) { if (k instanceof SecretKeySpec sk) { SharedSecrets.getJavaxCryptoSpecAccess() .clearSecretKeySpec(sk); + } else if (k instanceof PBKDF2KeyImpl p2k) { + p2k.clear(); } else { try { k.destroy(); diff --git a/src/java.base/share/classes/sun/security/util/KnownOIDs.java b/src/java.base/share/classes/sun/security/util/KnownOIDs.java index cbb0c1e0b5788..6c90801f69b3b 100644 --- a/src/java.base/share/classes/sun/security/util/KnownOIDs.java +++ b/src/java.base/share/classes/sun/security/util/KnownOIDs.java @@ -208,8 +208,9 @@ public enum KnownOIDs { PBEWithMD5AndRC2("1.2.840.113549.1.5.6"), PBEWithSHA1AndDES("1.2.840.113549.1.5.10"), PBEWithSHA1AndRC2("1.2.840.113549.1.5.11"), - PBKDF2WithHmacSHA1("1.2.840.113549.1.5.12"), + PBKDF2("1.2.840.113549.1.5.12", "PBKDF2WithHmacSHA1"), PBES2("1.2.840.113549.1.5.13"), + PBMAC1("1.2.840.113549.1.5.14"), // PKCS7 1.2.840.113549.1.7.* PKCS7("1.2.840.113549.1.7"), diff --git a/src/java.base/share/classes/sun/security/util/PBKDF2Parameters.java b/src/java.base/share/classes/sun/security/util/PBKDF2Parameters.java new file mode 100644 index 0000000000000..07d4c70fecb02 --- /dev/null +++ b/src/java.base/share/classes/sun/security/util/PBKDF2Parameters.java @@ -0,0 +1,212 @@ +/* + * 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 sun.security.util; + +import java.io.IOException; + +import sun.security.util.KnownOIDs; +import sun.security.x509.AlgorithmId; + +/** + * This class implements the parameter set used with password-based + * key derivation function 2 (PBKDF2), which is defined in PKCS#5 as follows: + * + *
+ *
+ * PBKDF2Algorithms ALGORITHM-IDENTIFIER ::=
+ *   { {PBKDF2-params IDENTIFIED BY id-PBKDF2}, ...}
+ *
+ * id-PBKDF2 OBJECT IDENTIFIER ::= {pkcs-5 12}
+ *
+ * PBKDF2-params ::= SEQUENCE {
+ *     salt CHOICE {
+ *       specified OCTET STRING,
+ *       otherSource AlgorithmIdentifier {{PBKDF2-SaltSources}}
+ *     },
+ *     iterationCount INTEGER (1..MAX),
+ *     keyLength INTEGER (1..MAX) OPTIONAL,
+ *     prf AlgorithmIdentifier {{PBKDF2-PRFs}} DEFAULT algid-hmacWithSHA1
+ * }
+ *
+ * PBKDF2-SaltSources ALGORITHM-IDENTIFIER ::= { ... }
+ *
+ * PBKDF2-PRFs ALGORITHM-IDENTIFIER ::= {
+ *     {NULL IDENTIFIED BY id-hmacWithSHA1} |
+ *     {NULL IDENTIFIED BY id-hmacWithSHA224} |
+ *     {NULL IDENTIFIED BY id-hmacWithSHA256} |
+ *     {NULL IDENTIFIED BY id-hmacWithSHA384} |
+ *     {NULL IDENTIFIED BY id-hmacWithSHA512}, ... }
+ *
+ * algid-hmacWithSHA1 AlgorithmIdentifier {{PBKDF2-PRFs}} ::=
+ *     {algorithm id-hmacWithSHA1, parameters NULL : NULL}
+ *
+ * id-hmacWithSHA1 OBJECT IDENTIFIER ::= {digestAlgorithm 7}
+ *
+ * For more information, see
+ * RFC 8018:
+ * PKCS #5: Password-Based Cryptography Specification.
+ *
+ * 
+ */ +public final class PBKDF2Parameters { + + private final byte[] salt; + + private final int iterationCount; + + // keyLength in bits, or -1 if not present + private final int keyLength; + + private final String prfAlgo; + + /** + * Initialize PBKDF2Parameters from a DER encoded + * parameter block. + * + * @param pBKDF2_params the DER encoding of the parameter block + * + * @throws IOException for parsing errors in the input stream + */ + public PBKDF2Parameters(DerValue pBKDF2_params) throws IOException { + + if (pBKDF2_params.tag != DerValue.tag_Sequence) { + throw new IOException("PBKDF2 parameter parsing error: " + + "not an ASN.1 SEQUENCE tag"); + } + DerValue specified = pBKDF2_params.data.getDerValue(); + // the 'specified' ASN.1 CHOICE for 'salt' is supported + if (specified.tag == DerValue.tag_OctetString) { + salt = specified.getOctetString(); + } else { + // the 'otherSource' ASN.1 CHOICE for 'salt' is not supported + throw new IOException("PBKDF2 parameter parsing error: " + + "not an ASN.1 OCTET STRING tag"); + } + iterationCount = pBKDF2_params.data.getInteger(); + + // keyLength INTEGER (1..MAX) OPTIONAL, + var ksDer = pBKDF2_params.data.getOptional(DerValue.tag_Integer); + if (ksDer.isPresent()) { + keyLength = ksDer.get().getInteger() * 8; // keyLength (in bits) + } else { + keyLength = -1; + } + + // prf AlgorithmIdentifier {{PBKDF2-PRFs}} DEFAULT algid-hmacWithSHA1 + var prfDer = pBKDF2_params.data.getOptional(DerValue.tag_Sequence); + if (prfDer.isPresent()) { + DerValue prf = prfDer.get(); + // the pseudorandom function (default is HmacSHA1) + ObjectIdentifier kdfAlgo_OID = prf.data.getOID(); + KnownOIDs o = KnownOIDs.findMatch(kdfAlgo_OID.toString()); + if (o == null || (!o.stdName().equals("HmacSHA1") && + !o.stdName().equals("HmacSHA224") && + !o.stdName().equals("HmacSHA256") && + !o.stdName().equals("HmacSHA384") && + !o.stdName().equals("HmacSHA512") && + !o.stdName().equals("HmacSHA512/224") && + !o.stdName().equals("HmacSHA512/256"))) { + throw new IOException("PBKDF2 parameter parsing error: " + + "expecting the object identifier for a HmacSHA " + + "pseudorandom function"); + } + prfAlgo = o.stdName(); + prf.data.getOptional(DerValue.tag_Null); + prf.data.atEnd(); + } else { + prfAlgo = "HmacSHA1"; + } + } + + public static byte[] encode(byte[] salt, int iterationCount, + int keyLength, String kdfHmac) { + ObjectIdentifier prf = + ObjectIdentifier.of(KnownOIDs.findMatch(kdfHmac)); + return PBKDF2Parameters.encode(salt, iterationCount, keyLength, prf); + } + + /* + * Encode PBKDF2 parameters from components. + * The outer algorithm ID is also encoded in addition to the parameters. + */ + public static byte[] encode(byte[] salt, int iterationCount, + int keyLength, ObjectIdentifier prf) { + assert keyLength != -1; + + DerOutputStream out = new DerOutputStream(); + DerOutputStream tmp0 = new DerOutputStream(); + + tmp0.putOctetString(salt); + tmp0.putInteger(iterationCount); + tmp0.putInteger(keyLength); + + // prf AlgorithmIdentifier {{PBKDF2-PRFs}} + tmp0.write(new AlgorithmId(prf)); + + // id-PBKDF2 OBJECT IDENTIFIER ::= {pkcs-5 12} + out.putOID(ObjectIdentifier.of(KnownOIDs.PBKDF2)); + out.write(DerValue.tag_Sequence, tmp0); + + return new DerOutputStream().write(DerValue.tag_Sequence, out) + .toByteArray(); + } + + /** + * Returns the salt. + * + * @return the salt + */ + public byte[] getSalt() { + return this.salt; + } + + /** + * Returns the iteration count. + * + * @return the iteration count + */ + public int getIterationCount() { + return this.iterationCount; + } + + /** + * Returns size of key generated by PBKDF2, or -1 if not found/set. + * + * @return size of key generated by PBKDF2, or -1 if not found/set + */ + public int getKeyLength() { + return this.keyLength; + } + + /** + * Returns name of Hmac. + * + * @return name of Hmac + */ + public String getPrfAlgo() { + return this.prfAlgo; + } +} diff --git a/src/java.base/share/conf/security/java.security b/src/java.base/share/conf/security/java.security index 2464361b9efe2..3d26c1dcd968a 100644 --- a/src/java.base/share/conf/security/java.security +++ b/src/java.base/share/conf/security/java.security @@ -1341,8 +1341,9 @@ jceks.key.serialFilter = java.base/java.lang.Enum;java.base/java.security.KeyRep #keystore.pkcs12.keyPbeIterationCount = 10000 # The algorithm used to calculate the optional MacData at the end of a PKCS12 -# file. This can be any HmacPBE algorithm defined in the Mac section of the -# Java Security Standard Algorithm Names Specification. When set to "NONE", +# file. This can be any HmacPBE or PBEWith algorithm defined in +# the Mac section of the Java Security Standard Algorithm Names Specification, +# for example, HmacPBESHA256 or PBEWithHmacSHA256. When set to "NONE", # no Mac is generated. The default value is "HmacPBESHA256". #keystore.pkcs12.macAlgorithm = HmacPBESHA256 diff --git a/test/jdk/sun/security/pkcs12/PBMAC1Test.java b/test/jdk/sun/security/pkcs12/PBMAC1Test.java new file mode 100644 index 0000000000000..acb0f73a2d249 --- /dev/null +++ b/test/jdk/sun/security/pkcs12/PBMAC1Test.java @@ -0,0 +1,223 @@ +/* + * 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. + * + * 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 8343232 + * @summary Verify correctness of the structure of PKCS12 PBMAC1 + * keystores created with various property values. + * Verify that keystores load correctly from an input stream. + * @modules java.base/sun.security.util + * @library /test/lib + */ +import jdk.test.lib.Asserts; +import jdk.test.lib.security.DerUtils; +import sun.security.util.KnownOIDs; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.security.KeyStore; +import java.security.NoSuchAlgorithmException; +import java.util.Base64; + +public class PBMAC1Test { + + static final char[] PASSWORD = "1234".toCharArray(); + + public static void main(String[] args) throws Exception { + create(); + migrate(); + overflow(); + } + + // PBMAC1 inside PKCS12 + //0019:008B [2] SEQUENCE + //001C:007B [20] SEQUENCE + //001E:0057 [200] SEQUENCE + //0020:000B [2000] OID 1.2.840.113549.1.5.14 (PBMAC1) + //002B:004A [2001] SEQUENCE + //002D:003A [20010] SEQUENCE + //002F:000B [200100] OID 1.2.840.113549.1.5.12 (PBKDF2) + //003A:002D [200101] SEQUENCE + //003C:0016 [2001010] OCTET STRING (20 bytes of salt) + //0052:0004 [2001011] INTEGER 10000 + //0056:0003 [2001012] INTEGER 32 + //0059:000E [2001013] SEQUENCE + //005B:000A [20010130] OID 1.2.840.113549.2.9 (HmacSHA256) + //0065:0002 [20010131] NULL + //0067:000E [20011] SEQUENCE + //0069:000A [200110] OID 1.2.840.113549.2.9 (HmacSHA256) + //0073:0002 [200111] NULL + //0075:0022 [201] OCTET STRING (32 bytes of mac) + //0097:000A [21] OCTET STRING (8 bytes of useless salt) + //00A1:0003 [22] INTEGER 1 + static void create() throws Exception { + System.setProperty("keystore.pkcs12.macAlgorithm", "pbewithhmacsha256"); + var der = emptyP12(); + DerUtils.checkAlg(der, "2000", KnownOIDs.PBMAC1); + DerUtils.checkAlg(der, "200100", KnownOIDs.PBKDF2); + DerUtils.checkAlg(der, "20010130", KnownOIDs.HmacSHA256); + DerUtils.checkAlg(der, "200110", KnownOIDs.HmacSHA256); + DerUtils.checkInt(der, "2001011", 10000); + DerUtils.checkInt(der, "2001012", 32); + + System.setProperty("keystore.pkcs12.macAlgorithm", "PBEWITHHMACSHA512"); + der = emptyP12(); + DerUtils.checkAlg(der, "2000", KnownOIDs.PBMAC1); + DerUtils.checkAlg(der, "200100", KnownOIDs.PBKDF2); + DerUtils.checkAlg(der, "20010130", KnownOIDs.HmacSHA512); + DerUtils.checkAlg(der, "200110", KnownOIDs.HmacSHA512); + DerUtils.checkInt(der, "2001011", 10000); + DerUtils.checkInt(der, "2001012", 64); + + System.setProperty("keystore.pkcs12.macAlgorithm", "PBEWiThHmAcSHA512/224"); + der = emptyP12(); + DerUtils.checkAlg(der, "2000", KnownOIDs.PBMAC1); + DerUtils.checkAlg(der, "200100", KnownOIDs.PBKDF2); + DerUtils.checkAlg(der, "20010130", KnownOIDs.HmacSHA512$224); + DerUtils.checkAlg(der, "200110", KnownOIDs.HmacSHA512$224); + DerUtils.checkInt(der, "2001011", 10000); + DerUtils.checkInt(der, "2001012", 28); + + // As strange as I can... + System.setProperty("keystore.pkcs12.macAlgorithm", + "PBEWithHmacSHA512/224AndHmacSHA3-384"); + der = emptyP12(); + DerUtils.checkAlg(der, "2000", KnownOIDs.PBMAC1); + DerUtils.checkAlg(der, "200100", KnownOIDs.PBKDF2); + DerUtils.checkAlg(der, "20010130", KnownOIDs.HmacSHA512$224); + DerUtils.checkAlg(der, "200110", KnownOIDs.HmacSHA3_384); + DerUtils.checkInt(der, "2001011", 10000); + DerUtils.checkInt(der, "2001012", 48); + + // Bad alg names + System.setProperty("keystore.pkcs12.macAlgorithm", "PBEWithHmacSHA456"); + var reason = Asserts.assertThrows(NoSuchAlgorithmException.class, + () -> emptyP12()).getMessage(); + Asserts.assertTrue(reason.contains("Algorithm hmacsha456 not available"), reason); + } + + static void migrate() throws Exception { + // A pkcs12 file using PBEWithHmacSHA256 but key length is 8 + var sha2p12 = """ + MIGhAgEDMBEGCSqGSIb3DQEHAaAEBAIwADCBiDB5MFUGCSqGSIb3DQEFDjBIMDgGCSqGSIb3DQEF + DDArBBSV6e5xI+9AYtGHQlDI0X4pmvWLBQICJxACAQgwDAYIKoZIhvcNAgkFADAMBggqhkiG9w0C + CQUABCAaaSO6JgEh1lDo1pvAC0CF5HqgIFBvzt1+GZlgFy7xFQQITk9UIFVTRUQCAQE= + """; + var der = Base64.getMimeDecoder().decode(sha2p12); + DerUtils.checkInt(der, "2001012", 8); // key length used to be 8 + + der = loadAndStore(sha2p12); + DerUtils.checkAlg(der, "20010130", KnownOIDs.HmacSHA256); + DerUtils.checkAlg(der, "200110", KnownOIDs.HmacSHA256); + DerUtils.checkInt(der, "2001012", 32); // key length changed to 32 + } + + static void overflow() throws Exception { + + // Cannot create new + System.setProperty("keystore.pkcs12.macIterationCount", "5000001"); + System.setProperty("keystore.pkcs12.macAlgorithm", "pbewithhmacsha256"); + Asserts.assertThrows(IllegalArgumentException.class, PBMAC1Test::emptyP12); + System.clearProperty("keystore.pkcs12.macAlgorithm"); + Asserts.assertThrows(IllegalArgumentException.class, PBMAC1Test::emptyP12); + + // IC=5000001 using old algorithm + var bigICt = """ + MGYCAQMwEQYJKoZIhvcNAQcBoAQEAjAAME4wMTANBglghkgBZQMEAgEFAAQgyLBK5h9/E/2o7l2A + eALbI1otiS8kT3C41Ef3T38OMjUEFIic7isrAJNr+3+8fUbnMtmB0qytAgNMS0E= + """; + + // IC=5000000 using old algorithm + var smallICt = """ + MGYCAQMwEQYJKoZIhvcNAQcBoAQEAjAAME4wMTANBglghkgBZQMEAgEFAAQgR61YZLW6H81rkGTk + XfuU138mkIugdoQBhuNsnvWuBtQEFJ0wmMlpoUiji8PlvwCrmMbqWW4XAgNMS0A= + """; + + // IC=5000001 using PBMAC1 + var bigICp = """ + MIGiAgEDMBEGCSqGSIb3DQEHAaAEBAIwADCBiTB6MFYGCSqGSIb3DQEFDjBJMDkGCSqGSIb3DQEF + DDAsBBQFNf/gHCO5jNT429D6Q5gxTKHqVAIDTEtBAgEgMAwGCCqGSIb3DQIJBQAwDAYIKoZIhvcN + AgkFAAQgwEVMcyMPQXJSXUIbWqNWjMArtnXDlNUGnKD+19B7QFkECE5PVCBVU0VEAgEB + """; + + // IC=5000000 using PBMAC1 + var smallICp = """ + MIGiAgEDMBEGCSqGSIb3DQEHAaAEBAIwADCBiTB6MFYGCSqGSIb3DQEFDjBJMDkGCSqGSIb3DQEF + DDAsBBS/ZFfC7swsDHvaCXwyQkuMrZ7dbgIDTEtAAgEgMAwGCCqGSIb3DQIJBQAwDAYIKoZIhvcN + AgkFAAQgCRvE7LDbzkcYOVv/7iBv0KB3DoUkwnpTI0nsonVfv9UECE5PVCBVU0VEAgEB"""; + + loadAndStore(smallICp); + loadAndStore(smallICt); + + Asserts.assertTrue(Asserts.assertThrows(IOException.class, () -> loadAndStore(bigICp)) + .getMessage().contains("MAC iteration count too large: 5000001")); + Asserts.assertTrue(Asserts.assertThrows(IOException.class, () -> loadAndStore(bigICt)) + .getMessage().contains("MAC iteration count too large: 5000001")); + + // Incorrect Salt + var incorrectSalt = """ + MIGdAgEDMBEGCSqGSIb3DQEHAaAEBAIwADCBhDB1MFEGCSqGSIb3DQEFDjBEMDYGCSqGSIb3DQEF + DDApBBSakVhBLltKvqUj6EAxvWqJi+gc7AICJxACASAwCgYIKoZIhvcNAgkwCgYIKoZIhvcNAgkE + IG+euEHE8iN/2C7txbCjCJ9mU4TgEsHPsC9L3Rxa7malBAhOT1QgVVNFRAIBAQ=="""; + Asserts.assertTrue(Asserts.assertThrows(IOException.class, () -> loadAndStore(incorrectSalt)) + .getMessage().contains("Integrity check failed")); + + // Incorrect Iteration Count + var incorrectIC = """ + MIGdAgEDMBEGCSqGSIb3DQEHAaAEBAIwADCBhDB1MFEGCSqGSIb3DQEFDjBEMDYGCSqGSIb3DQEF + DDApBBSZkVhBLltKvqUj6EAxvWqJi+gc7AICKBACASAwCgYIKoZIhvcNAgkwCgYIKoZIhvcNAgkE + IG+euEHE8iN/2C7txbCjCJ9mU4TgEsHPsC9L3Rxa7malBAhOT1QgVVNFRAIBAQ=="""; + Asserts.assertTrue(Asserts.assertThrows(IOException.class, () -> loadAndStore(incorrectIC)) + .getMessage().contains("Integrity check failed")); + + // Missing Key Length + var missingKeyLength = """ + MIGaAgEDMBEGCSqGSIb3DQEHAaAEBAIwADCBgTByME4GCSqGSIb3DQEFDjBBMDMGCSqGSIb3DQEF + DDAmBBSZkVhBLltKvqUj6EAxvWqJi+gc7AICJxAwCgYIKoZIhvcNAgkwCgYIKoZIhvcNAgkEIG+e + uEHE8iN/2C7txbCjCJ9mU4TgEsHPsC9L3Rxa7malBAhOT1QgVVNFRAIBAQ=="""; + Asserts.assertTrue(Asserts.assertThrows(IOException.class, () -> loadAndStore(missingKeyLength)) + .getMessage().contains("missing keyLength field")); + } + + static byte[] emptyP12() throws Exception { + var ks = KeyStore.getInstance("pkcs12"); + ks.load(null, null); + var os = new ByteArrayOutputStream(); + ks.store(os, PASSWORD); + return os.toByteArray(); + } + + static byte[] loadAndStore(String data) throws Exception { + var bytes = Base64.getMimeDecoder().decode(data); + var ks = KeyStore.getInstance("PKCS12"); + ks.load(new ByteArrayInputStream(bytes), PASSWORD); + var baos = new ByteArrayOutputStream(); + ks.store(baos, PASSWORD); + var newBytes = baos.toByteArray(); + var bais = new ByteArrayInputStream(newBytes); + ks.load(bais, PASSWORD); + return newBytes; + } +} diff --git a/test/jdk/sun/security/pkcs12/ParamsPreferences.java b/test/jdk/sun/security/pkcs12/ParamsPreferences.java index 4bedca56a786c..c40bd4f4b7059 100644 --- a/test/jdk/sun/security/pkcs12/ParamsPreferences.java +++ b/test/jdk/sun/security/pkcs12/ParamsPreferences.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -35,7 +35,7 @@ /* * @test - * @bug 8076190 8242151 8153005 8266293 + * @bug 8076190 8242151 8153005 8266293 8343232 * @library /test/lib * @modules java.base/sun.security.pkcs * java.base/sun.security.util @@ -244,7 +244,7 @@ static void test(int n, Map sysProps, checkAlg(data, "110c10", EncryptedData); checkAlg(data, "110c110110", certAlg); if (certAlg == PBES2) { - checkAlg(data, "110c11011100", PBKDF2WithHmacSHA1); + checkAlg(data, "110c11011100", PBKDF2); checkAlg(data, "110c1101110130", (KnownOIDs)args[i++]); checkAlg(data, "110c11011110", (KnownOIDs)args[i++]); checkInt(data, "110c110111011", (int) args[i++]); @@ -257,7 +257,7 @@ static void test(int n, Map sysProps, KnownOIDs keyAlg = (KnownOIDs)args[i++]; checkAlg(data, "110c010c01000", keyAlg); if (keyAlg == PBES2) { - checkAlg(data, "110c010c0100100", PBKDF2WithHmacSHA1); + checkAlg(data, "110c010c0100100", PBKDF2); checkAlg(data, "110c010c010010130", (KnownOIDs)args[i++]); checkAlg(data, "110c010c0100110", (KnownOIDs)args[i++]); checkInt(data, "110c010c01001011", (int) args[i++]);