From 2a1ae0ff89a8ac364206b09059d9dc884adcc5ac Mon Sep 17 00:00:00 2001 From: Kevin Driver Date: Tue, 5 Nov 2024 21:07:52 +0000 Subject: [PATCH] 8331008: Implement JEP 478: Key Derivation Function API (Preview) Co-authored-by: Rajan Halade Co-authored-by: Weijun Wang Co-authored-by: Valerie Peng Reviewed-by: weijun, valeriep --- .../crypto/provider/HKDFKeyDerivation.java | 414 +++++++++++ .../com/sun/crypto/provider/SunJCE.java | 10 + .../share/classes/java/security/Provider.java | 2 + .../share/classes/javax/crypto/KDF.java | 681 ++++++++++++++++++ .../classes/javax/crypto/KDFParameters.java | 50 ++ .../share/classes/javax/crypto/KDFSpi.java | 157 ++++ .../javax/crypto/spec/HKDFParameterSpec.java | 510 +++++++++++++ .../jdk/internal/javac/PreviewFeature.java | 2 + .../classes/sun/security/util/Debug.java | 2 +- .../provider/KDF/HKDFBasicFunctionsTest.java | 80 ++ .../provider/KDF/HKDFExhaustiveTest.java | 556 ++++++++++++++ .../provider/KDF/HKDFKnownAnswerTests.java | 282 ++++++++ .../crypto/provider/KDF/HKDFSaltIKMTest.java | 92 +++ .../KDF/KDFDelayedProviderSyncTest.java | 73 ++ .../crypto/KDF/KDFDelayedProviderTest.java | 180 +++++ .../KDF/KDFDelayedProviderThreadingTest.java | 130 ++++ .../javax/crypto/ProviderVerifier.java | 101 +++ 17 files changed, 3321 insertions(+), 1 deletion(-) create mode 100644 src/java.base/share/classes/com/sun/crypto/provider/HKDFKeyDerivation.java create mode 100644 src/java.base/share/classes/javax/crypto/KDF.java create mode 100644 src/java.base/share/classes/javax/crypto/KDFParameters.java create mode 100644 src/java.base/share/classes/javax/crypto/KDFSpi.java create mode 100644 src/java.base/share/classes/javax/crypto/spec/HKDFParameterSpec.java create mode 100644 test/jdk/com/sun/crypto/provider/KDF/HKDFBasicFunctionsTest.java create mode 100644 test/jdk/com/sun/crypto/provider/KDF/HKDFExhaustiveTest.java create mode 100644 test/jdk/com/sun/crypto/provider/KDF/HKDFKnownAnswerTests.java create mode 100644 test/jdk/com/sun/crypto/provider/KDF/HKDFSaltIKMTest.java create mode 100644 test/jdk/javax/crypto/KDF/KDFDelayedProviderSyncTest.java create mode 100644 test/jdk/javax/crypto/KDF/KDFDelayedProviderTest.java create mode 100644 test/jdk/javax/crypto/KDF/KDFDelayedProviderThreadingTest.java create mode 100644 test/jdk/security/unsignedjce/java.base/javax/crypto/ProviderVerifier.java diff --git a/src/java.base/share/classes/com/sun/crypto/provider/HKDFKeyDerivation.java b/src/java.base/share/classes/com/sun/crypto/provider/HKDFKeyDerivation.java new file mode 100644 index 00000000000..a9988bbc115 --- /dev/null +++ b/src/java.base/share/classes/com/sun/crypto/provider/HKDFKeyDerivation.java @@ -0,0 +1,414 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * 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 com.sun.crypto.provider; + +import javax.crypto.KDFSpi; +import javax.crypto.Mac; +import javax.crypto.SecretKey; +import javax.crypto.ShortBufferException; +import javax.crypto.spec.HKDFParameterSpec; +import javax.crypto.spec.SecretKeySpec; +import java.io.ByteArrayOutputStream; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import javax.crypto.KDFParameters; +import java.security.NoSuchAlgorithmException; +import java.security.ProviderException; +import java.security.spec.AlgorithmParameterSpec; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * KDF implementation for the HKDF function. + *

+ * This class implements the HKDF-Extract and HKDF-Expand functions from RFC + * 5869. This implementation provides the complete Extract-then-Expand HKDF + * function as well as Extract-only and Expand-only variants. + * + * @spec https://www.rfc-editor.org/info/rfc5869 + * RFC 5869: HMAC-based Extract-and-Expand Key Derivation Function (HKDF) + */ +abstract class HKDFKeyDerivation extends KDFSpi { + + private final int hmacLen; + private final String hmacAlgName; + + private enum SupportedHmac { + SHA256("HmacSHA256", 32), + SHA384("HmacSHA384", 48), + SHA512("HmacSHA512", 64); + + private final String hmacAlg; + private final int hmacLen; + SupportedHmac(String hmacAlg, int hmacLen) { + this.hmacAlg = hmacAlg; + this.hmacLen = hmacLen; + } + }; + + /** + * The sole constructor. + * + * @param kdfParameters + * the initialization parameters (may be {@code null}) + * + * @throws InvalidAlgorithmParameterException + * if the initialization parameters are inappropriate for this + * {@code KDFSpi} + */ + private HKDFKeyDerivation(SupportedHmac supportedHmac, + KDFParameters kdfParameters) + throws InvalidAlgorithmParameterException { + super(kdfParameters); + if (kdfParameters != null) { + throw new InvalidAlgorithmParameterException( + supportedHmac.hmacAlg + " does not support parameters"); + } + this.hmacAlgName = supportedHmac.hmacAlg; + this.hmacLen = supportedHmac.hmacLen; + } + + /** + * Derive a key, returned as a {@code SecretKey} object. + * + * @return a derived {@code SecretKey} object of the specified algorithm + * + * @throws InvalidAlgorithmParameterException + * if the information contained within the {@code derivationSpec} is + * invalid or if the combination of {@code alg} and the + * {@code derivationSpec} results in something invalid + * @throws NoSuchAlgorithmException + * if {@code alg} is empty + * @throws NullPointerException + * if {@code alg} is {@code null} + */ + @Override + protected SecretKey engineDeriveKey(String alg, + AlgorithmParameterSpec derivationSpec) + throws InvalidAlgorithmParameterException, + NoSuchAlgorithmException { + + if (alg == null) { + throw new NullPointerException( + "the algorithm for the SecretKey return value must not be" + + " null"); + } + if (alg.isEmpty()) { + throw new NoSuchAlgorithmException( + "the algorithm for the SecretKey return value must not be " + + "empty"); + } + + return new SecretKeySpec(engineDeriveData(derivationSpec), alg); + + } + + /** + * Obtain raw data from a key derivation function. + * + * @return a derived {@code byte[]} + * + * @throws InvalidAlgorithmParameterException + * if the information contained within the {@code KDFParameterSpec} + * is invalid or incorrect for the type of key to be derived + * @throws UnsupportedOperationException + * if the derived keying material is not extractable + */ + @Override + protected byte[] engineDeriveData(AlgorithmParameterSpec derivationSpec) + throws InvalidAlgorithmParameterException { + List ikms, salts; + byte[] inputKeyMaterial, salt, pseudoRandomKey, info; + int length; + if (derivationSpec instanceof HKDFParameterSpec.Extract anExtract) { + ikms = anExtract.ikms(); + salts = anExtract.salts(); + // we should be able to combine both of the above Lists of key + // segments into one SecretKey object each, unless we were passed + // something bogus or an unexportable P11 key + inputKeyMaterial = null; + salt = null; + try { + inputKeyMaterial = consolidateKeyMaterial(ikms); + salt = consolidateKeyMaterial(salts); + + // perform extract + return hkdfExtract(inputKeyMaterial, salt); + } catch (InvalidKeyException ike) { + throw new InvalidAlgorithmParameterException( + "an HKDF Extract could not be initialized with the " + + "given key or salt material", ike); + } catch (NoSuchAlgorithmException nsae) { + // This is bubbling up from the getInstance of the Mac/Hmac. + // Since we're defining these values internally, it is unlikely. + throw new ProviderException( + "could not instantiate a Mac with the provided " + + "algorithm", + nsae); + } finally { + if (inputKeyMaterial != null) { + Arrays.fill(inputKeyMaterial, (byte) 0x00); + } + if (salt != null) { + Arrays.fill(salt, (byte) 0x00); + } + } + } else if (derivationSpec instanceof HKDFParameterSpec.Expand anExpand) { + // set this value in the "if" + if ((pseudoRandomKey = anExpand.prk().getEncoded()) == null) { + throw new AssertionError( + "PRK is required for HKDFParameterSpec.Expand"); + } + // set this value in the "if" + if ((info = anExpand.info()) == null) { + info = new byte[0]; + } + length = anExpand.length(); + if (length > (hmacLen * 255)) { + throw new InvalidAlgorithmParameterException( + "Requested length exceeds maximum allowed length"); + } + // perform expand + try { + return hkdfExpand(pseudoRandomKey, info, length); + } catch (InvalidKeyException ike) { + throw new InvalidAlgorithmParameterException( + "an HKDF Expand could not be initialized with the " + + "given keying material", ike); + } catch (NoSuchAlgorithmException nsae) { + // This is bubbling up from the getInstance of the Mac/Hmac. + // Since we're defining these values internally, it is unlikely. + throw new ProviderException( + "could not instantiate a Mac with the provided " + + "algorithm", + nsae); + } finally { + Arrays.fill(pseudoRandomKey, (byte) 0x00); + } + } else if (derivationSpec instanceof HKDFParameterSpec.ExtractThenExpand anExtractThenExpand) { + ikms = anExtractThenExpand.ikms(); + salts = anExtractThenExpand.salts(); + // we should be able to combine both of the above Lists of key + // segments into one SecretKey object each, unless we were passed + // something bogus or an unexportable P11 key + inputKeyMaterial = null; + salt = null; + pseudoRandomKey = null; + try { + inputKeyMaterial = consolidateKeyMaterial(ikms); + salt = consolidateKeyMaterial(salts); + + // set this value in the "if" + if ((info = anExtractThenExpand.info()) == null) { + info = new byte[0]; + } + length = anExtractThenExpand.length(); + if (length > (hmacLen * 255)) { + throw new InvalidAlgorithmParameterException( + "Requested length exceeds maximum allowed length"); + } + + // perform extract and then expand + pseudoRandomKey = hkdfExtract(inputKeyMaterial, salt); + return hkdfExpand(pseudoRandomKey, info, length); + } catch (InvalidKeyException ike) { + throw new InvalidAlgorithmParameterException( + "an HKDF ExtractThenExpand could not be initialized " + + "with the given key or salt material", ike); + } catch (NoSuchAlgorithmException nsae) { + // This is bubbling up from the getInstance of the Mac/HMAC. + // Since we're defining these values internally, it is unlikely. + throw new ProviderException( + "could not instantiate a Mac with the provided " + + "algorithm", + nsae); + } finally { + if (inputKeyMaterial != null) { + Arrays.fill(inputKeyMaterial, (byte) 0x00); + } + if (salt != null) { + Arrays.fill(salt, (byte) 0x00); + } + if (pseudoRandomKey != null) { + Arrays.fill(pseudoRandomKey, (byte) 0x00); + } + } + } + throw new InvalidAlgorithmParameterException( + "an HKDF derivation requires a valid HKDFParameterSpec"); + } + + // throws an InvalidKeyException if any key is unextractable + private byte[] consolidateKeyMaterial(List keys) + throws InvalidKeyException { + if (keys != null && !keys.isEmpty()) { + ArrayList localKeys = new ArrayList<>(keys); + if (localKeys.size() == 1) { + // return this element + SecretKey checkIt = localKeys.get(0); + return CipherCore.getKeyBytes(checkIt); + } else { + ByteArrayOutputStream os = new ByteArrayOutputStream(); + for (SecretKey workItem : localKeys) { + os.writeBytes(CipherCore.getKeyBytes(workItem)); + } + // deliberately omitting os.flush(), since we are writing to + // memory, and toByteArray() reads like there isn't an explicit + // need for this call + return os.toByteArray(); + } + } else if (keys != null) { + return new byte[0]; + } else { + throw new InvalidKeyException( + "List of key segments could not be consolidated"); + } + } + + /** + * Perform the HKDF-Extract operation. + * + * @param inputKeyMaterial + * the input keying material used for the HKDF-Extract operation. + * @param salt + * the salt value used for HKDF-Extract + * + * @return a byte array containing the pseudorandom key (PRK) + * + * @throws InvalidKeyException + * if an invalid salt was provided through the + * {@code HKDFParameterSpec} + */ + private byte[] hkdfExtract(byte[] inputKeyMaterial, byte[] salt) + throws InvalidKeyException, NoSuchAlgorithmException { + + // salt will not be null + if (salt.length == 0) { + salt = new byte[hmacLen]; + } + Mac hmacObj = Mac.getInstance(hmacAlgName); + hmacObj.init(new SecretKeySpec(salt, hmacAlgName)); + + // inputKeyMaterial will not be null + return hmacObj.doFinal(inputKeyMaterial); + } + + /** + * Perform the HKDF-Expand operation. + * + * @param prk + * the pseudorandom key used for HKDF-Expand + * @param info + * optional context and application specific information or + * {@code null} if no info data is provided. + * @param outLen + * the length in bytes of the required output + * + * @return a byte array containing the complete {@code KDF} output. This + * will be at least as long as the requested length in the + * {@code outLen} parameter, but will be rounded up to the nearest + * multiple of the HMAC output length. + * + * @throws InvalidKeyException + * if an invalid PRK was provided through the + * {@code HKDFParameterSpec} or derived during the extract phase. + */ + private byte[] hkdfExpand(byte[] prk, byte[] info, int outLen) + throws InvalidKeyException, NoSuchAlgorithmException { + byte[] kdfOutput; + + if (prk == null || prk.length < hmacLen) { + throw new InvalidKeyException( + "prk must be at least " + hmacLen + " bytes"); + } + + SecretKey pseudoRandomKey = new SecretKeySpec(prk, hmacAlgName); + + Mac hmacObj = Mac.getInstance(hmacAlgName); + + // Calculate the number of rounds of HMAC that are needed to + // meet the requested data. Then set up the buffers we will need. + hmacObj.init(pseudoRandomKey); + int rounds = (outLen + hmacLen - 1) / hmacLen; + kdfOutput = new byte[outLen]; + int i = 0; + int offset = 0; + try { + while (i < rounds) { + if (i > 0) { + hmacObj.update(kdfOutput, offset - hmacLen, + hmacLen); // add T(i-1) + } + hmacObj.update(info); // Add info + hmacObj.update((byte) ++i); // Add round number + if (i == rounds && (outLen - offset < hmacLen)) { + // special handling for last chunk + byte[] tmp = hmacObj.doFinal(); + System.arraycopy(tmp, 0, kdfOutput, offset, + outLen - offset); + Arrays.fill(tmp, (byte) 0x00); + offset = outLen; + } else { + hmacObj.doFinal(kdfOutput, offset); + offset += hmacLen; + } + } + } catch (ShortBufferException sbe) { + // This really shouldn't happen given that we've + // sized the buffers to their largest possible size up-front, + // but just in case... + throw new ProviderException(sbe); + } + return kdfOutput; + } + + protected KDFParameters engineGetParameters() { + return null; + } + + public static final class HKDFSHA256 extends HKDFKeyDerivation { + public HKDFSHA256(KDFParameters kdfParameters) + throws InvalidAlgorithmParameterException { + super(SupportedHmac.SHA256, kdfParameters); + } + } + + public static final class HKDFSHA384 extends HKDFKeyDerivation { + public HKDFSHA384(KDFParameters kdfParameters) + throws InvalidAlgorithmParameterException { + super(SupportedHmac.SHA384, kdfParameters); + } + } + + public static final class HKDFSHA512 extends HKDFKeyDerivation { + public HKDFSHA512(KDFParameters kdfParameters) + throws InvalidAlgorithmParameterException { + super(SupportedHmac.SHA512, kdfParameters); + } + } + +} \ No newline at end of file diff --git a/src/java.base/share/classes/com/sun/crypto/provider/SunJCE.java b/src/java.base/share/classes/com/sun/crypto/provider/SunJCE.java index 9bb1fa7ff82..c0766077ba9 100644 --- a/src/java.base/share/classes/com/sun/crypto/provider/SunJCE.java +++ b/src/java.base/share/classes/com/sun/crypto/provider/SunJCE.java @@ -457,6 +457,16 @@ public final class SunJCE extends Provider { "com.sun.crypto.provider.DHKeyAgreement", attrs); + /* + * Key Derivation engines + */ + ps("KDF", "HKDF-SHA256", + "com.sun.crypto.provider.HKDFKeyDerivation$HKDFSHA256"); + ps("KDF", "HKDF-SHA384", + "com.sun.crypto.provider.HKDFKeyDerivation$HKDFSHA384"); + ps("KDF", "HKDF-SHA512", + "com.sun.crypto.provider.HKDFKeyDerivation$HKDFSHA512"); + /* * Algorithm Parameter engines */ diff --git a/src/java.base/share/classes/java/security/Provider.java b/src/java.base/share/classes/java/security/Provider.java index adfbd9a5957..c4dac74c680 100644 --- a/src/java.base/share/classes/java/security/Provider.java +++ b/src/java.base/share/classes/java/security/Provider.java @@ -27,6 +27,7 @@ package java.security; import jdk.internal.event.SecurityProviderServiceEvent; +import javax.crypto.KDFParameters; import javax.security.auth.login.Configuration; import java.io.*; import java.security.cert.CertStoreParameters; @@ -1604,6 +1605,7 @@ public abstract class Provider extends Properties { addEngine("KeyGenerator", false, null); addEngine("SecretKeyFactory", false, null); addEngine("KEM", true, null); + addEngine("KDF", false, KDFParameters.class); // JSSE addEngine("KeyManagerFactory", false, null); addEngine("SSLContext", false, null); diff --git a/src/java.base/share/classes/javax/crypto/KDF.java b/src/java.base/share/classes/javax/crypto/KDF.java new file mode 100644 index 00000000000..ea2a0375813 --- /dev/null +++ b/src/java.base/share/classes/javax/crypto/KDF.java @@ -0,0 +1,681 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * 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 javax.crypto; + +import jdk.internal.javac.PreviewFeature; +import sun.security.jca.GetInstance; +import sun.security.jca.GetInstance.Instance; +import sun.security.util.Debug; + +import java.security.InvalidAlgorithmParameterException; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.Security; +import java.security.Provider; +import java.security.Provider.Service; +import java.security.spec.AlgorithmParameterSpec; +import java.util.Iterator; +import java.util.Objects; + +/** + * This class provides the functionality of a Key Derivation Function (KDF), + * which is a cryptographic algorithm for deriving additional keys from input + * keying material (IKM) and (optionally) other data. + *

+ * {@code KDF} objects are instantiated with the {@code getInstance} family of + * methods. + *

+ * The class has two derive methods, {@code deriveKey} and {@code deriveData}. + * The {@code deriveKey} method accepts an algorithm name and returns a + * {@code SecretKey} object with the specified algorithm. The {@code deriveData} + * method returns a byte array of raw data. + *

+ * API Usage Example: + * {@snippet lang = java: + * KDF kdfHkdf = KDF.getInstance("HKDF-SHA256"); + * + * AlgorithmParameterSpec derivationSpec = + * HKDFParameterSpec.ofExtract() + * .addIKM(ikm) + * .addSalt(salt).thenExpand(info, 32); + * + * SecretKey sKey = kdfHkdf.deriveKey("AES", derivationSpec); + *} + *
+ *

Concurrent Access

+ * Unless otherwise documented by an implementation, the methods defined in this + * class are not thread-safe. Multiple threads that need to access a single + * object concurrently should synchronize amongst themselves and provide the + * necessary locking. Multiple threads each manipulating separate objects need + * not synchronize. + *
+ *

Delayed Provider Selection

+ * If a provider is not specified when calling one of the {@code getInstance} + * methods, the implementation delays the selection of the provider until the + * {@code deriveKey} or {@code deriveData} method is called. This is called + * delayed provider selection. The primary reason this is done is to + * ensure that the selected provider can handle the key material that is passed + * to those methods - for example, the key material may reside on a hardware + * device that only a specific {@code KDF} provider can utilize. The {@code + * getInstance} method returns a {@code KDF} object as long as there exists + * at least one registered security provider that implements the algorithm + * and supports the optional parameters. The delayed provider selection + * process traverses the list of registered security providers, starting with + * the most preferred {@code Provider}. The first provider that supports the + * specified algorithm, optional parameters, and key material is selected. + *

+ * If the {@code getProviderName} or {@code getParameters} method is called + * before the {@code deriveKey} or {@code deriveData} methods, the first + * provider supporting the {@code KDF} algorithm and optional + * {@code KDFParameters} is chosen. This provider may not support the key + * material that is subsequently passed to the {@code deriveKey} or + * {@code deriveData} methods. Therefore, it is recommended not to call the + * {@code getProviderName} or {@code getParameters} methods until after a key + * derivation operation. Once a provider is selected, it cannot be changed. + * + * @see KDFParameters + * @see SecretKey + * @since 24 + */ +@PreviewFeature(feature = PreviewFeature.Feature.KEY_DERIVATION) +public final class KDF { + + private static final Debug pdebug = Debug.getInstance("provider", + "Provider"); + private static final boolean skipDebug = Debug.isOn("engine=") + && !Debug.isOn("kdf"); + + private record Delegate(KDFSpi spi, Provider provider) {} + + //guarded by 'lock' + private Delegate theOne; + //guarded by 'lock' + private final Delegate candidate; + + // The name of the KDF algorithm. + private final String algorithm; + + // Additional KDF configuration parameters + private final KDFParameters kdfParameters; + + // remaining services to try in provider selection + // null once provider is selected + private final Iterator serviceIterator; + + // This lock provides mutual exclusion, preventing multiple threads from + // concurrently initializing the same instance (delayed provider selection) + // in a way which would corrupt the internal state. + private final Object lock = new Object(); + + + // Instantiates a {@code KDF} object. This constructor is called when a + // provider is supplied to {@code getInstance}. + // + // @param delegate the delegate + // @param algorithm the algorithm + // @param kdfParameters the parameters + private KDF(Delegate delegate, String algorithm) { + this.theOne = delegate; + this.algorithm = algorithm; + // note that the parameters are being passed to the impl in getInstance + this.kdfParameters = null; + this.candidate = null; + serviceIterator = null; + } + + // Instantiates a {@code KDF} object. This constructor is called when a + // provider is not supplied to {@code getInstance}. + // + // @param firstPairOfSpiAndProv the delegate + // @param t the service iterator + // @param algorithm the algorithm + // @param kdfParameters the algorithm parameters + private KDF(Delegate firstPairOfSpiAndProv, Iterator t, + String algorithm, + KDFParameters kdfParameters) { + this.candidate = firstPairOfSpiAndProv; + serviceIterator = t; + this.algorithm = algorithm; + this.kdfParameters = kdfParameters; + } + + /** + * Returns the algorithm name of this {@code KDF} object. + * + * @return the algorithm name of this {@code KDF} object + */ + public String getAlgorithm() { + return this.algorithm; + } + + /** + * Returns the name of the provider. + * + * @return the name of the provider + * + * @see Delayed Provider + * Selection + */ + public String getProviderName() { + useFirstSpi(); + return theOne.provider().getName(); + } + + /** + * Returns the {@code KDFParameters} used with this {@code KDF} object. + *

+ * The returned parameters may be the same that were used to initialize + * this {@code KDF} object, or may contain additional default or + * random parameter values used by the underlying KDF algorithm. + * If the required parameters were not supplied and can be generated by + * the {@code KDF} object, the generated parameters are returned; + * otherwise {@code null} is returned. + * + * @return the parameters used with this {@code KDF} object, or + * {@code null} + * + * @see Delayed Provider + * Selection + */ + public KDFParameters getParameters() { + useFirstSpi(); + return theOne.spi().engineGetParameters(); + } + + /** + * Returns a {@code KDF} object that implements the specified algorithm. + * + * @implNote The JDK Reference Implementation additionally uses the + * {@code jdk.security.provider.preferred} + * {@link Security#getProperty(String) Security} property to + * determine the preferred provider order for the specified + * algorithm. This may be different than the order of providers + * returned by + * {@link Security#getProviders() Security.getProviders()}. + * + * @param algorithm + * the key derivation algorithm to use. See the {@code KDF} section + * in the + * Java Security Standard Algorithm Names Specification for + * information about standard KDF algorithm names. + * + * @return a {@code KDF} object + * + * @throws NoSuchAlgorithmException + * if no {@code Provider} supports a {@code KDF} implementation for + * the specified algorithm + * @throws NullPointerException + * if {@code algorithm} is {@code null} + * @see Delayed Provider + * Selection + */ + public static KDF getInstance(String algorithm) + throws NoSuchAlgorithmException { + Objects.requireNonNull(algorithm, "algorithm must not be null"); + try { + return getInstance(algorithm, (KDFParameters) null); + } catch (InvalidAlgorithmParameterException e) { + throw new NoSuchAlgorithmException( + "No implementation found using null KDFParameters", e); + } + } + + /** + * Returns a {@code KDF} object that implements the specified algorithm from + * the specified security provider. The specified provider must be + * registered in the security provider list. + * + * @param algorithm + * the key derivation algorithm to use. See the {@code KDF} section + * in the + * Java Security Standard Algorithm Names Specification for + * information about standard KDF algorithm names. + * @param provider + * the provider to use for this key derivation + * + * @return a {@code KDF} object + * + * @throws NoSuchAlgorithmException + * if the specified provider does not support the specified + * {@code KDF} algorithm + * @throws NoSuchProviderException + * if the specified provider is not registered in the security + * provider list + * @throws NullPointerException + * if {@code algorithm} or {@code provider} is {@code null} + */ + public static KDF getInstance(String algorithm, String provider) + throws NoSuchAlgorithmException, NoSuchProviderException { + Objects.requireNonNull(algorithm, "algorithm must not be null"); + Objects.requireNonNull(provider, "provider must not be null"); + try { + return getInstance(algorithm, null, provider); + } catch (InvalidAlgorithmParameterException e) { + throw new NoSuchAlgorithmException( + "No implementation found using null KDFParameters", e); + } + } + + /** + * Returns a {@code KDF} object that implements the specified algorithm from + * the specified security provider. + * + * @param algorithm + * the key derivation algorithm to use. See the {@code KDF} section + * in the + * Java Security Standard Algorithm Names Specification for + * information about standard KDF algorithm names. + * @param provider + * the provider to use for this key derivation + * + * @return a {@code KDF} object + * + * @throws NoSuchAlgorithmException + * if the specified provider does not support the specified + * {@code KDF} algorithm + * @throws NullPointerException + * if {@code algorithm} or {@code provider} is {@code null} + */ + public static KDF getInstance(String algorithm, Provider provider) + throws NoSuchAlgorithmException { + Objects.requireNonNull(algorithm, "algorithm must not be null"); + Objects.requireNonNull(provider, "provider must not be null"); + try { + return getInstance(algorithm, null, provider); + } catch (InvalidAlgorithmParameterException e) { + throw new NoSuchAlgorithmException( + "No implementation found using null KDFParameters", e); + } + } + + /** + * Returns a {@code KDF} object that implements the specified algorithm and + * is initialized with the specified parameters. + * + * @implNote The JDK Reference Implementation additionally uses the + * {@code jdk.security.provider.preferred} + * {@link Security#getProperty(String) Security} property to + * determine the preferred provider order for the specified + * algorithm. This may be different than the order of providers + * returned by + * {@link Security#getProviders() Security.getProviders()}. + * + * @param algorithm + * the key derivation algorithm to use. See the {@code KDF} section + * in the + * Java Security Standard Algorithm Names Specification for + * information about standard KDF algorithm names. + * @param kdfParameters + * the {@code KDFParameters} used to configure the derivation + * algorithm or {@code null} if no parameters are provided + * + * @return a {@code KDF} object + * + * @throws NoSuchAlgorithmException + * if no {@code Provider} supports a {@code KDF} implementation for + * the specified algorithm + * @throws InvalidAlgorithmParameterException + * if at least one {@code Provider} supports a {@code KDF} + * implementation for the specified algorithm but none of them + * support the specified parameters + * @throws NullPointerException + * if {@code algorithm} is {@code null} + * @see Delayed Provider + * Selection + */ + public static KDF getInstance(String algorithm, + KDFParameters kdfParameters) + throws NoSuchAlgorithmException, InvalidAlgorithmParameterException { + Objects.requireNonNull(algorithm, "algorithm must not be null"); + // make sure there is at least one service from a signed provider + Iterator t = GetInstance.getServices("KDF", algorithm); + + Delegate d = getNext(t, kdfParameters); + return (t.hasNext() ? + new KDF(d, t, algorithm, kdfParameters) : + new KDF(d, algorithm)); + } + + /** + * Returns a {@code KDF} object that implements the specified algorithm from + * the specified provider and is initialized with the specified parameters. + * The specified provider must be registered in the security provider list. + * + * @param algorithm + * the key derivation algorithm to use. See the {@code KDF} section + * in the + * Java Security Standard Algorithm Names Specification for + * information about standard KDF algorithm names. + * @param kdfParameters + * the {@code KDFParameters} used to configure the derivation + * algorithm or {@code null} if no parameters are provided + * @param provider + * the provider to use for this key derivation + * + * @return a {@code KDF} object + * + * @throws NoSuchAlgorithmException + * if the specified provider does not support the specified + * {@code KDF} algorithm + * @throws NoSuchProviderException + * if the specified provider is not registered in the security + * provider list + * @throws InvalidAlgorithmParameterException + * if the specified provider supports the specified {@code KDF} + * algorithm but does not support the specified parameters + * @throws NullPointerException + * if {@code algorithm} or {@code provider} is {@code null} + */ + public static KDF getInstance(String algorithm, + KDFParameters kdfParameters, + String provider) + throws NoSuchAlgorithmException, NoSuchProviderException, + InvalidAlgorithmParameterException { + Objects.requireNonNull(algorithm, "algorithm must not be null"); + Objects.requireNonNull(provider, "provider must not be null"); + + Instance instance = GetInstance.getInstance("KDF", KDFSpi.class, + algorithm, + kdfParameters, + provider); + if (!JceSecurity.canUseProvider(instance.provider)) { + String msg = "JCE cannot authenticate the provider " + + instance.provider.getName(); + throw new NoSuchProviderException(msg); + } + return new KDF(new Delegate((KDFSpi) instance.impl, + instance.provider), algorithm + ); + } + + /** + * Returns a {@code KDF} object that implements the specified algorithm from + * the specified provider and is initialized with the specified parameters. + * + * @param algorithm + * the key derivation algorithm to use. See the {@code KDF} section + * in the + * Java Security Standard Algorithm Names Specification for + * information about standard KDF algorithm names. + * @param kdfParameters + * the {@code KDFParameters} used to configure the derivation + * algorithm or {@code null} if no parameters are provided + * @param provider + * the provider to use for this key derivation + * + * @return a {@code KDF} object + * + * @throws NoSuchAlgorithmException + * if the specified provider does not support the specified + * {@code KDF} algorithm + * @throws InvalidAlgorithmParameterException + * if the specified provider supports the specified {@code KDF} + * algorithm but does not support the specified parameters + * @throws NullPointerException + * if {@code algorithm} or {@code provider} is {@code null} + */ + public static KDF getInstance(String algorithm, + KDFParameters kdfParameters, + Provider provider) + throws NoSuchAlgorithmException, + InvalidAlgorithmParameterException { + Objects.requireNonNull(algorithm, "algorithm must not be null"); + Objects.requireNonNull(provider, "provider must not be null"); + Instance instance = GetInstance.getInstance("KDF", KDFSpi.class, + algorithm, + kdfParameters, + provider); + if (!JceSecurity.canUseProvider(instance.provider)) { + String msg = "JCE cannot authenticate the provider " + + instance.provider.getName(); + throw new SecurityException(msg); + } + return new KDF(new Delegate((KDFSpi) instance.impl, + instance.provider), algorithm + ); + } + + /** + * Derives a key, returned as a {@code SecretKey} object. + * + * @param alg + * the algorithm of the resultant {@code SecretKey} object + * @param derivationSpec + * the object describing the inputs to the derivation function + * + * @return the derived key + * + * @throws InvalidAlgorithmParameterException + * if the information contained within the {@code derivationSpec} is + * invalid or if the combination of {@code alg} and the + * {@code derivationSpec} results in something invalid + * @throws NoSuchAlgorithmException + * if {@code alg} is empty or invalid + * @throws NullPointerException + * if {@code alg} or {@code derivationSpec} is null + * + * @see Delayed Provider + * Selection + * + */ + public SecretKey deriveKey(String alg, + AlgorithmParameterSpec derivationSpec) + throws InvalidAlgorithmParameterException, + NoSuchAlgorithmException { + if (alg == null) { + throw new NullPointerException( + "the algorithm for the SecretKey return value must not be" + + " null"); + } + if (alg.isEmpty()) { + throw new NoSuchAlgorithmException( + "the algorithm for the SecretKey return value must not be " + + "empty"); + } + Objects.requireNonNull(derivationSpec); + if (checkSpiNonNull(theOne)) { + return theOne.spi().engineDeriveKey(alg, derivationSpec); + } else { + return (SecretKey) chooseProvider(alg, derivationSpec); + } + } + + /** + * Derives a key, returns raw data as a byte array. + * + * @param derivationSpec + * the object describing the inputs to the derivation function + * + * @return the derived key in its raw bytes + * + * @throws InvalidAlgorithmParameterException + * if the information contained within the {@code derivationSpec} is + * invalid + * @throws UnsupportedOperationException + * if the derived keying material is not extractable + * @throws NullPointerException + * if {@code derivationSpec} is null + * + * @see Delayed Provider + * Selection + * + */ + public byte[] deriveData(AlgorithmParameterSpec derivationSpec) + throws InvalidAlgorithmParameterException { + + Objects.requireNonNull(derivationSpec); + if (checkSpiNonNull(theOne)) { + return theOne.spi().engineDeriveData(derivationSpec); + } else { + try { + return (byte[]) chooseProvider(null, derivationSpec); + } catch (NoSuchAlgorithmException e) { + // this will never be thrown in the deriveData case + throw new AssertionError(e); + } + } + } + + /** + * Use the firstSpi as the chosen KDFSpi and set the fields accordingly + */ + private void useFirstSpi() { + if (checkSpiNonNull(theOne)) return; + + synchronized (lock) { + if (!checkSpiNonNull(theOne)) { + theOne = candidate; + } + } + } + + /** + * Selects the provider which supports the passed {@code algorithm} and + * {@code derivationSpec} values, and assigns the global spi and provider + * variables if they have not been assigned yet. + *

+ * If the spi has already been set, it will just return the result. + */ + private Object chooseProvider(String algorithm, + AlgorithmParameterSpec derivationSpec) + throws InvalidAlgorithmParameterException, + NoSuchAlgorithmException { + + boolean isDeriveData = (algorithm == null); + + synchronized (lock) { + if (checkSpiNonNull(theOne)) { + return (isDeriveData) ? + theOne.spi().engineDeriveData(derivationSpec) : + theOne.spi().engineDeriveKey(algorithm, derivationSpec); + } + + Exception lastException = null; + if (!checkSpiNonNull(candidate)) { + throw new AssertionError("Unexpected Error: candidate is null!"); + } + Delegate currOne = candidate; + try { + while (true) { + try { + Object result = (isDeriveData) ? + currOne.spi().engineDeriveData(derivationSpec) : + currOne.spi().engineDeriveKey(algorithm, + derivationSpec); + // found a working KDFSpi + this.theOne = currOne; + return result; + } catch (Exception e) { + if (!skipDebug && pdebug != null) { + pdebug.println("A " + this.getAlgorithm() + + " derivation cannot be performed " + + "using the supplied derivation " + + "inputs, using " + + currOne.provider().getName() + + ". Another Provider will be " + + "attempted."); + e.printStackTrace(pdebug.getPrintStream()); + } + if (lastException == null) { + lastException = e; + } + // try next one if available + assert serviceIterator != null : "serviceIterator was null"; + currOne = getNext(serviceIterator, kdfParameters); + } + } + } catch (InvalidAlgorithmParameterException e) { + throw e; // getNext reached end and have seen IAPE + } catch (NoSuchAlgorithmException e) { + if (!skipDebug && pdebug != null) { + pdebug.println( + "All available Providers have been examined " + + "without a match for performing the " + + this.getAlgorithm() + + " derivation using the supplied derivation " + + "inputs. Therefore, the caught " + + "NoSuchAlgorithmException will be logged, and " + + "an InvalidAlgorithmParameterException will " + + "then be thrown with the last known Exception."); + e.printStackTrace(pdebug.getPrintStream()); + } + // getNext reached end without finding an implementation + throw new InvalidAlgorithmParameterException(lastException); + } + } + } + + private static Delegate getNext(Iterator serviceIter, + KDFParameters kdfParameters) + throws NoSuchAlgorithmException, InvalidAlgorithmParameterException { + // fetch next one if available + boolean hasOne = false; + while (serviceIter.hasNext()) { + Service s = serviceIter.next(); + Provider prov = s.getProvider(); + if (!JceSecurity.canUseProvider(prov)) { + // continue to next iteration + continue; + } + hasOne = true; + try { + Object obj = s.newInstance(kdfParameters); + if (!(obj instanceof KDFSpi)) { + if (!skipDebug && pdebug != null) { + pdebug.println( + "obj was not an instance of KDFSpi (should not " + + "happen)"); + } + continue; + } + return new Delegate((KDFSpi) obj, prov); + } catch (NoSuchAlgorithmException nsae) { + // continue to the next provider + if (!skipDebug && pdebug != null) { + pdebug.println("A derivation cannot be performed " + + "using the supplied KDFParameters, using " + + prov.getName() + + ". Another Provider will be attempted."); + nsae.printStackTrace(pdebug.getPrintStream()); + } + } + } + if (!skipDebug && pdebug != null) { + pdebug.println( + "No provider can be found that supports the " + + "specified algorithm and parameters"); + } + if (hasOne) throw new InvalidAlgorithmParameterException( + "The KDFParameters supplied could not be used in combination " + + "with the supplied algorithm for the selected Provider"); + else throw new NoSuchAlgorithmException(); + } + + private static boolean checkSpiNonNull(Delegate d) { + // d.spi() cannot be null if d != null + return (d != null); + } +} diff --git a/src/java.base/share/classes/javax/crypto/KDFParameters.java b/src/java.base/share/classes/javax/crypto/KDFParameters.java new file mode 100644 index 00000000000..5f83204f3c4 --- /dev/null +++ b/src/java.base/share/classes/javax/crypto/KDFParameters.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * 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 javax.crypto; + +import jdk.internal.javac.PreviewFeature; + +/** + * A specification of Key Derivation Function ({@link KDF}) parameters. + *

+ * The purpose of this interface is to group (and provide type safety for) all + * {@code KDF} parameter specifications. All {@code KDF} parameter + * specifications must implement this interface. + *

+ * When supplied, the + * {@link KDF#getInstance(String, KDFParameters) KDF.getInstance} methods return + * a {@code KDF} that is initialized with the specified parameters. + *

+ * The {@code KDFParameters} used for initialization are returned by + * {@link KDF#getParameters()} and may contain additional default or random + * parameter values used by the underlying KDF implementation. + * + * @see KDF#getInstance(String, KDFParameters) + * @see KDF#getParameters() + * @see KDF + * @since 24 + */ +@PreviewFeature(feature = PreviewFeature.Feature.KEY_DERIVATION) +public interface KDFParameters {} diff --git a/src/java.base/share/classes/javax/crypto/KDFSpi.java b/src/java.base/share/classes/javax/crypto/KDFSpi.java new file mode 100644 index 00000000000..dcd2029c0c0 --- /dev/null +++ b/src/java.base/share/classes/javax/crypto/KDFSpi.java @@ -0,0 +1,157 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * 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 javax.crypto; + +import jdk.internal.javac.PreviewFeature; + +import java.security.InvalidAlgorithmParameterException; +import java.security.NoSuchAlgorithmException; +import java.security.spec.AlgorithmParameterSpec; + +/** + * This class defines the Service Provider Interface (SPI) for the + * Key Derivation Function ({@link KDF}) class. + *

+ * All the abstract methods in this class must be implemented by each + * cryptographic service provider who wishes to supply the implementation of a + * particular key derivation function algorithm. + *

+ * Implementations must provide a public constructor which accepts a {@code + * KDFParameters} object if they depend on the default implementation of + * {@code Provider.Service.newInstance} to construct {@code KDFSpi} instances. + * The constructor must call {@code super(params)} passing the parameters + * supplied. The constructor must also throw an + * {@code InvalidAlgorithmParameterException} if the supplied parameters are + * inappropriate. If a {@code KDF} object is instantiated with one of the + * {@code getInstance} methods that contains a {@code KDFParameters} parameter, + * the user-provided {@code KDFParameters} object will be passed to the + * constructor of the {@code KDFSpi} implementation. Otherwise, if it is + * instantiated with one of the {@code getInstance} methods without a + * {@code KDFParameters} parameter, a {@code null} value will be passed to the + * constructor. + *

+ * Implementations which do not support {@code KDFParameters} must require + * {@code null} to be passed, otherwise an + * {@code InvalidAlgorithmParameterException} will be thrown. On the other hand, + * implementations which require {@code KDFParameters} should throw an + * {@code InvalidAlgorithmParameterException} upon receiving a {@code null} + * value if default parameters cannot be generated or upon receiving {@code + * KDFParameters} which are not supported by the implementation. + *

+ * To aid the caller, implementations may return parameters with additional + * default values or supply random values as used by the underlying {@code KDF} + * algorithm. See {@link KDFSpi#engineGetParameters()} for more details. + * + * @see KDF + * @see KDFParameters + * @see KDF#getParameters() + * @see SecretKey + * @since 24 + */ +@PreviewFeature(feature = PreviewFeature.Feature.KEY_DERIVATION) +public abstract class KDFSpi { + + /** + * The sole constructor. + *

+ * A {@code KDFParameters} object may be specified for KDF algorithms that + * support initialization parameters. + * + * @param kdfParameters + * the initialization parameters for the {@code KDF} algorithm (may + * be {@code null}) + * + * @throws InvalidAlgorithmParameterException + * if the initialization parameters are inappropriate for this + * {@code KDFSpi} + * @see KDF#getParameters() + */ + protected KDFSpi(KDFParameters kdfParameters) + throws InvalidAlgorithmParameterException {} + + /** + * Returns the {@code KDFParameters} used with this {@code KDF} object. + *

+ * The returned parameters may be the same that were used to initialize + * this {@code KDF} object, or may contain additional default or + * random parameter values used by the underlying KDF algorithm. + * If the required parameters were not supplied and can be generated by + * the {@code KDF} object, the generated parameters are returned; + * otherwise {@code null} is returned. + * + * @return the parameters used with this {@code KDF} object, or + * {@code null} + */ + protected abstract KDFParameters engineGetParameters(); + + /** + * Derives a key, returned as a {@code SecretKey} object. + * + * @implNote If the resultant key is extractable, then its + * {@code getEncoded} value should have the same content as the + * result of {@code deriveData}. + * + * @param alg + * the algorithm of the resultant {@code SecretKey} object + * @param derivationSpec + * derivation parameters + * + * @return the derived key. + * + * @throws InvalidAlgorithmParameterException + * if the information contained within the {@code derivationSpec} is + * invalid or if the combination of {@code alg} and the + * {@code derivationSpec} results in something invalid + * @throws NoSuchAlgorithmException + * if {@code alg} is empty or invalid + * @throws NullPointerException + * if {@code alg} or {@code derivationSpec} is null + */ + protected abstract SecretKey engineDeriveKey(String alg, + AlgorithmParameterSpec derivationSpec) + throws InvalidAlgorithmParameterException, NoSuchAlgorithmException; + + /** + * Derives a key, returns raw data as a byte array. + * + * @param derivationSpec + * derivation parameters + * + * @return the derived key in its raw bytes. + * + * @throws InvalidAlgorithmParameterException + * if the information contained within the {@code derivationSpec} is + * invalid + * @throws UnsupportedOperationException + * if the derived keying material is not extractable + * @throws NullPointerException + * if {@code derivationSpec} is null + */ + protected abstract byte[] engineDeriveData( + AlgorithmParameterSpec derivationSpec) + throws InvalidAlgorithmParameterException; + +} \ No newline at end of file diff --git a/src/java.base/share/classes/javax/crypto/spec/HKDFParameterSpec.java b/src/java.base/share/classes/javax/crypto/spec/HKDFParameterSpec.java new file mode 100644 index 00000000000..8f697d12e60 --- /dev/null +++ b/src/java.base/share/classes/javax/crypto/spec/HKDFParameterSpec.java @@ -0,0 +1,510 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * 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 javax.crypto.spec; + +import jdk.internal.javac.PreviewFeature; + +import javax.crypto.SecretKey; +import java.security.spec.AlgorithmParameterSpec; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * Parameters for the combined Extract, Expand, or Extract-then-Expand + * operations of the HMAC-based Key Derivation Function (HKDF). The HKDF + * function is defined in RFC + * 5869. + *

+ * In the Extract and Extract-then-Expand cases, users may call the {@code + * addIKM} and/or {@code addSalt} methods repeatedly (and chain these calls). + * This provides for use-cases where a portion of the input keying material + * (IKM) resides in a non-extractable {@code SecretKey} and the whole IKM + * cannot be provided as a single object. The same feature is available for + * salts. + *

+ * The above feature is particularly useful for "labeled" HKDF Extract used in + * TLS 1.3 and HPKE, where the IKM consists of concatenated components, which + * may include both byte arrays and (possibly non-extractable) secret keys. + *

+ * Examples: + * {@snippet lang = java: + * // this usage depicts the initialization of an HKDF-Extract AlgorithmParameterSpec + * AlgorithmParameterSpec derivationSpec = + * HKDFParameterSpec.ofExtract() + * .addIKM(label) + * .addIKM(ikm) + * .addSalt(salt).extractOnly(); + *} + * {@snippet lang = java: + * // this usage depicts the initialization of an HKDF-Expand AlgorithmParameterSpec + * AlgorithmParameterSpec derivationSpec = + * HKDFParameterSpec.expandOnly(prk, info, 32); + *} + * {@snippet lang = java: + * // this usage depicts the initialization of an HKDF-ExtractExpand AlgorithmParameterSpec + * AlgorithmParameterSpec derivationSpec = + * HKDFParameterSpec.ofExtract() + * .addIKM(ikm) + * .addSalt(salt).thenExpand(info, 32); + *} + * + * @spec https://www.rfc-editor.org/info/rfc5869 + * RFC 5869: HMAC-based Extract-and-Expand Key Derivation Function (HKDF) + * @see javax.crypto.KDF + * @since 24 + */ +@PreviewFeature(feature = PreviewFeature.Feature.KEY_DERIVATION) +public interface HKDFParameterSpec extends AlgorithmParameterSpec { + + /** + * This {@code Builder} builds {@code Extract} and {@code ExtractThenExpand} + * objects. + *

+ * The {@code Builder} is initialized via the {@code ofExtract} method of + * {@code HKDFParameterSpec}. As stated in the class description, + * {@code addIKM} and/or {@code addSalt} may be called as needed. Finally, + * an object is "built" by calling either {@code extractOnly} or + * {@code thenExpand} for {@code Extract} and {@code ExtractThenExpand} + * use-cases respectively. Note that the {@code Builder} is not + * thread-safe. + */ + @PreviewFeature(feature = PreviewFeature.Feature.KEY_DERIVATION) + final class Builder { + + private List ikms = new ArrayList<>(); + private List salts = new ArrayList<>(); + + private Builder() {} + + /** + * Builds an {@code Extract} object from the current state of the + * {@code Builder}. + * + * @return an immutable {@code Extract} object + */ + public Extract extractOnly() { + return new Extract(ikms, salts); + } + + /** + * Builds an {@code ExtractThenExpand} object from the current state of + * the {@code Builder}. + * + * @implNote HKDF implementations will enforce that the length + * is not greater than 255 * HMAC length. HKDF implementations + * will also enforce that a {code null} info value is treated as + * zero-length byte array. + * + * @param info + * the optional context and application specific information + * (may be {@code null}); the byte array is cloned to prevent + * subsequent modification + * @param length + * the length of the output keying material (must be greater + * than 0) + * + * @return an immutable {@code ExtractThenExpand} object + * + * @throws IllegalArgumentException + * if {@code length} is not greater than 0 + */ + public ExtractThenExpand thenExpand(byte[] info, int length) { + return new ExtractThenExpand( + extractOnly(), info, + length); + } + + /** + * Adds input keying material (IKM) to the builder. + *

+ * Users may call {@code addIKM} multiple times when the input keying + * material value is to be assembled piece-meal or if part of the IKM is + * to be supplied by a hardware crypto device. The {@code ikms()} + * method of the {@code Extract} or {@code ExtractThenExpand} object + * that is subsequently built returns the assembled input keying + * material as a list of {@code SecretKey} objects. + * + * @param ikm + * the input keying material (IKM) value + * + * @return this builder + * + * @throws NullPointerException + * if the {@code ikm} argument is null + */ + public Builder addIKM(SecretKey ikm) { + Objects.requireNonNull(ikm, "ikm must not be null"); + ikms.add(ikm); + return this; + } + + /** + * Adds input keying material (IKM) to the builder. Note that an + * {@code ikm} byte array of length zero will be discarded. + *

+ * Users may call {@code addIKM} multiple times when the input keying + * material value is to be assembled piece-meal or if part of the IKM is + * to be supplied by a hardware crypto device. The {@code ikms()} + * method of the {@code Extract} or {@code ExtractThenExpand} object + * that is subsequently built returns the assembled input keying + * material as a list of {@code SecretKey} objects. + * + * @param ikm + * the input keying material (IKM) value; the {@code ikm} + * byte array will be converted to a {@code SecretKeySpec}, + * which means that the byte array will be cloned inside the + * {@code SecretKeySpec} constructor + * + * @return this builder + * + * @throws NullPointerException + * if the {@code ikm} argument is null + */ + public Builder addIKM(byte[] ikm) { + Objects.requireNonNull(ikm, "ikm must not be null"); + if (ikm.length != 0) { + return addIKM(new SecretKeySpec(ikm, "Generic")); + } else { + return this; + } + } + + /** + * Adds a salt to the builder. + *

+ * Users may call {@code addSalt} multiple times when the salt value is + * to be assembled piece-meal or if part of the salt is to be supplied + * by a hardware crypto device. The {@code salts()} method of the + * {@code Extract} or {@code ExtractThenExpand} object that is + * subsequently built returns the assembled salt as a list of + * {@code SecretKey} objects. + * + * @param salt + * the salt value + * + * @return this builder + * + * @throws NullPointerException + * if the {@code salt} is null + */ + public Builder addSalt(SecretKey salt) { + Objects.requireNonNull(salt, "salt must not be null"); + salts.add(salt); + return this; + } + + /** + * Adds a salt to the builder. Note that a {@code salt} byte array of + * length zero will be discarded. + *

+ * Users may call {@code addSalt} multiple times when the salt value is + * to be assembled piece-meal or if part of the salt is to be supplied + * by a hardware crypto device. The {@code salts()} method of the + * {@code Extract} or {@code ExtractThenExpand} object that is + * subsequently built returns the assembled salt as a list of + * {@code SecretKey} objects. + * + * @param salt + * the salt value; the {@code salt} byte array will be + * converted to a {@code SecretKeySpec}, which means that the + * byte array will be cloned inside the {@code SecretKeySpec} + * constructor + * + * @return this builder + * + * @throws NullPointerException + * if the {@code salt} is null + */ + public Builder addSalt(byte[] salt) { + Objects.requireNonNull(salt, "salt must not be null"); + if (salt.length != 0) { + return addSalt(new SecretKeySpec(salt, "Generic")); + } else { + return this; + } + } + } + + /** + * Returns a {@code Builder} for building {@code Extract} and + * {@code ExtractThenExpand} objects. + * + * @return a new {@code Builder} + */ + static Builder ofExtract() { + return new Builder(); + } + + /** + * Creates an {@code Expand} object. + * + * @implNote HKDF implementations will enforce that the length is + * not greater than 255 * HMAC length. Implementations will also + * enforce that the prk argument is at least as many bytes as the + * HMAC length. Implementations will also enforce that a {code null} + * info value is treated as zero-length byte array. + * + * @param prk + * the pseudorandom key (PRK); must not be {@code null} + * @param info + * the optional context and application specific information (may be + * {@code null}); the byte array is cloned to prevent subsequent + * modification + * @param length + * the length of the output keying material (must be greater than + * 0) + * + * @return an {@code Expand} object + * + * @throws NullPointerException + * if the {@code prk} argument is {@code null} + * @throws IllegalArgumentException + * if {@code length} is not greater than 0 + */ + static Expand expandOnly(SecretKey prk, byte[] info, int length) { + if (prk == null) { + throw new NullPointerException("prk must not be null"); + } + return new Expand(prk, info, length); + } + + /** + * Defines the input parameters of an Extract operation as defined in RFC 5869. + */ + @PreviewFeature(feature = PreviewFeature.Feature.KEY_DERIVATION) + final class Extract implements HKDFParameterSpec { + + // HKDF-Extract(salt, IKM) -> PRK + private final List ikms; + private final List salts; + + private Extract(List ikms, List salts) { + this.ikms = List.copyOf(ikms); + this.salts = List.copyOf(salts); + } + + /** + * Returns an unmodifiable {@code List} of input keying material values + * in the order they were added. Returns an empty list if there are no + * input keying material values. + *

+ * Input keying material values added by {@link Builder#addIKM(byte[])} + * are converted to a {@code SecretKeySpec} object. Empty arrays are + * discarded. + * + * @implNote An HKDF implementation should concatenate the input + * keying materials into a single value to be used in + * HKDF-Extract. + * + * @return the unmodifiable {@code List} of input keying material + * values + */ + public List ikms() { + return ikms; + } + + /** + * Returns an unmodifiable {@code List} of salt values in the order they + * were added. Returns an empty list if there are no salt values. + *

+ * Salt values added by {@link Builder#addSalt(byte[])} are converted to + * a {@code SecretKeySpec} object. Empty arrays are discarded. + * + * @implNote An HKDF implementation should concatenate the salts + * into a single value to be used in HKDF-Extract. + * + * @return the unmodifiable {@code List} of salt values + */ + public List salts() { + return salts; + } + + } + + /** + * Defines the input parameters of an Expand operation as defined in RFC 5869. + */ + @PreviewFeature(feature = PreviewFeature.Feature.KEY_DERIVATION) + final class Expand implements HKDFParameterSpec { + + // HKDF-Expand(PRK, info, L) -> OKM + private final SecretKey prk; + private final byte[] info; + private final int length; + + /** + * Constructor that may be used to initialize an {@code Expand} object + * + * @param prk + * the pseudorandom key (PRK); in the case of + * {@code ExtractThenExpand}, the {@code prk} argument may be + * {@null} since the output of extract phase is used + * @param info + * the optional context and application specific information + * (may be {@code null}); the byte array is cloned to prevent + * subsequent modification + * @param length + * the length of the output keying material + * + * @throws IllegalArgumentException + * if {@code length} not greater than 0 + */ + private Expand(SecretKey prk, byte[] info, int length) { + // a null prk argument could be indicative of ExtractThenExpand + this.prk = prk; + this.info = (info == null) ? null : info.clone(); + if (!(length > 0)) { + throw new IllegalArgumentException("length must be > 0"); + } + this.length = length; + } + + /** + * Returns the pseudorandom key (PRK). + * + * @return the pseudorandom key + */ + public SecretKey prk() { + return prk; + } + + /** + * Returns the optional context and application specific information. + * + * @return a clone of the optional context and application specific + * information, or {@code null} if not specified + */ + public byte[] info() { + return (info == null) ? null : info.clone(); + } + + /** + * Returns the length of the output keying material. + * + * @return the length of the output keying material + */ + public int length() { + return length; + } + + } + + /** + * Defines the input parameters of an Extract-then-Expand operation as + * defined in RFC 5869. + */ + @PreviewFeature(feature = PreviewFeature.Feature.KEY_DERIVATION) + final class ExtractThenExpand implements HKDFParameterSpec { + private final Extract ext; + private final Expand exp; + + /** + * Constructor that may be used to initialize an + * {@code ExtractThenExpand} object + * + * @param ext + * a pre-generated {@code Extract} + * @param info + * the optional context and application specific information + * (may be {@code null}); the byte array is cloned to prevent + * subsequent modification + * @param length + * the length of the output keying material + * + * @throws IllegalArgumentException + * if {@code length} is not greater than 0 + */ + private ExtractThenExpand(Extract ext, byte[] info, int length) { + Objects.requireNonNull(ext, "Extract object must not be null"); + this.ext = ext; + // - null prk argument is ok here (it's a signal) + // - {@code Expand} constructor can deal with a null info + // - length is checked in {@code Expand} constructor + this.exp = new Expand(null, info, length); + } + + /** + * Returns an unmodifiable {@code List} of input keying material values + * in the order they were added. Returns an empty list if there are no + * input keying material values. + *

+ * Input keying material values added by {@link Builder#addIKM(byte[])} + * are converted to a {@code SecretKeySpec} object. Empty arrays are + * discarded. + * + * @implNote An HKDF implementation should concatenate the input + * keying materials into a single value to be used in the + * HKDF-Extract phase. + * + * @return the unmodifiable {@code List} of input keying material + * values + */ + public List ikms() { + return ext.ikms(); + } + + /** + * Returns an unmodifiable {@code List} of salt values in the order they + * were added. Returns an empty list if there are no salt values. + *

+ * Salt values added by {@link Builder#addSalt(byte[])} are converted to + * a {@code SecretKeySpec} object. Empty arrays are discarded. + * + * @implNote An HKDF implementation should concatenate the salts + * into a single value to be used in the HKDF-Extract phase. + * + * @return the unmodifiable {@code List} of salt values + * + */ + public List salts() { + return ext.salts(); + } + + /** + * Returns the optional context and application specific information. + * + * @return a clone of the optional context and application specific + * information, or {@code null} if not specified + */ + public byte[] info() { + return exp.info(); + } + + /** + * Returns the length of the output keying material. + * + * @return the length of the output keying material + */ + public int length() { + return exp.length(); + } + + } + +} diff --git a/src/java.base/share/classes/jdk/internal/javac/PreviewFeature.java b/src/java.base/share/classes/jdk/internal/javac/PreviewFeature.java index 6ac3bfd8bce..483093e66eb 100644 --- a/src/java.base/share/classes/jdk/internal/javac/PreviewFeature.java +++ b/src/java.base/share/classes/jdk/internal/javac/PreviewFeature.java @@ -80,6 +80,8 @@ public @interface PreviewFeature { STREAM_GATHERERS, @JEP(number=476, title="Module Import Declarations", status="Preview") MODULE_IMPORTS, + @JEP(number=478, title="Key Derivation Function API", status="Preview") + KEY_DERIVATION, LANGUAGE_MODEL, /** * A key for testing. diff --git a/src/java.base/share/classes/sun/security/util/Debug.java b/src/java.base/share/classes/sun/security/util/Debug.java index f1484145f5c..65e121421b8 100644 --- a/src/java.base/share/classes/sun/security/util/Debug.java +++ b/src/java.base/share/classes/sun/security/util/Debug.java @@ -139,7 +139,7 @@ public class Debug { System.err.println("engine="); System.err.println(" only dump output for the specified list"); System.err.println(" of JCA engines. Supported values:"); - System.err.println(" Cipher, KeyAgreement, KeyGenerator,"); + System.err.println(" Cipher, KDF, KeyAgreement, KeyGenerator,"); System.err.println(" KeyPairGenerator, KeyStore, Mac,"); System.err.println(" MessageDigest, SecureRandom, Signature."); System.err.println(); diff --git a/test/jdk/com/sun/crypto/provider/KDF/HKDFBasicFunctionsTest.java b/test/jdk/com/sun/crypto/provider/KDF/HKDFBasicFunctionsTest.java new file mode 100644 index 00000000000..2697ba57641 --- /dev/null +++ b/test/jdk/com/sun/crypto/provider/KDF/HKDFBasicFunctionsTest.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * 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 8331008 + * @summary basic HKDF operations + * @library /test/lib + * @enablePreview + */ + +import java.util.HexFormat; +import javax.crypto.KDF; +import javax.crypto.spec.HKDFParameterSpec; +import jdk.test.lib.Asserts; + +public class HKDFBasicFunctionsTest { + public static void main(String[] args) throws Exception { + var ikm = HexFormat.of().parseHex("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"); + var salt = HexFormat.of().parseHex("000102030405060708090a0b0c"); + var info = HexFormat.of().parseHex("f0f1f2f3f4f5f6f7f8f9"); + var len = 42; + + var kdf = KDF.getInstance("HKDF-SHA256"); + var expectedPrk = HexFormat.of().parseHex("077709362c2e32df0ddc3f0dc47bba6390b6c73bb50f9c3122ec844ad7c2b3e5"); + var expectedOkm = HexFormat.of().parseHex("3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c5bf34007208d5b887185865"); + + var extractOnly = HKDFParameterSpec.ofExtract().addIKM(ikm).addSalt(salt).extractOnly(); + var prk = kdf.deriveKey("PRK", extractOnly); + var expandOnly = HKDFParameterSpec.expandOnly(prk, info, len); + var okm1 = kdf.deriveKey("OKM", expandOnly); + var extractAndExpand = HKDFParameterSpec.ofExtract().addIKM(ikm).addSalt(salt).thenExpand(info, len); + var okm2 = kdf.deriveKey("OKM", extractAndExpand); + + Asserts.assertEqualsByteArray(prk.getEncoded(), expectedPrk, + "the PRK must match the expected value"); + + Asserts.assertEqualsByteArray(okm1.getEncoded(), expectedOkm, + "the OKM must match the expected value " + + "(expand)"); + + Asserts.assertEqualsByteArray(okm2.getEncoded(), expectedOkm, + "the OKM must match the expected value " + + "(extract expand)"); + + // test empty extract + test(HKDFParameterSpec.ofExtract().extractOnly()); + // test expand with empty info + test(HKDFParameterSpec.ofExtract().thenExpand(new byte[0], 32)); + // test expand with null info + test(HKDFParameterSpec.ofExtract().thenExpand(null, 32)); + // test extract with zero-length salt + test(HKDFParameterSpec.ofExtract().addIKM(ikm).addSalt(new byte[0]).extractOnly()); + } + + static void test(HKDFParameterSpec p) throws Exception { + var kdf = KDF.getInstance("HKDF-SHA256"); + System.out.println(HexFormat.of().formatHex(kdf.deriveData(p))); + } +} diff --git a/test/jdk/com/sun/crypto/provider/KDF/HKDFExhaustiveTest.java b/test/jdk/com/sun/crypto/provider/KDF/HKDFExhaustiveTest.java new file mode 100644 index 00000000000..0088fe54a80 --- /dev/null +++ b/test/jdk/com/sun/crypto/provider/KDF/HKDFExhaustiveTest.java @@ -0,0 +1,556 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * 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 8331008 + * @summary KDF API tests + * @library /test/lib + * @run main/othervm -Djava.security.egd=file:/dev/urandom -Djava.security.debug=provider,engine=kdf HKDFExhaustiveTest + * @enablePreview + */ + +import java.security.InvalidAlgorithmParameterException; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.Provider; +import java.security.Security; +import java.security.spec.AlgorithmParameterSpec; +import java.security.spec.InvalidParameterSpecException; +import java.util.ArrayList; +import java.util.List; +import javax.crypto.KDF; +import javax.crypto.KDFParameters; +import javax.crypto.SecretKey; +import javax.crypto.spec.HKDFParameterSpec; +import javax.crypto.spec.SecretKeySpec; +import jdk.test.lib.Asserts; +import jdk.test.lib.Utils; + +public class HKDFExhaustiveTest { + + private static final String JDK_HKDF_SHA256 = "HKDF-SHA256"; + private static final String JDK_HKDF_SHA384 = "HKDF-SHA384"; + private static final String JDK_HKDF_SHA512 = "HKDF-SHA512"; + private static final String[] KDF_ALGORITHMS = { + JDK_HKDF_SHA256, JDK_HKDF_SHA384, JDK_HKDF_SHA512 + }; + private static final String SUNJCE = "SunJCE"; + + // SECRET_KEY_SPEC_KEYS and RAW_DATA holds valid values for IKM and SALTS + private static final List SECRET_KEY_SPEC_KEYS = + List.of( + new SecretKeySpec(new byte[] {0}, "HKDF-IKM"), + new SecretKeySpec("IKM".getBytes(), "HKDF-IKM")); + private static final List RAW_DATA = List.of(new byte[] {0}, "RAW".getBytes()); + + private static final byte[] EMPTY = new byte[0]; + private static final int SHORT_LENGTH = 42; + private static final int LARGE_LENGTH = 1000; + private static final int NEGATIVE_LENGTH = -1; + + private static final KdfVerifier KdfGetInstanceVerifier = + (a, p, s) -> { + + // Test KDF getInstance methods, all should have same algo and provider + KDF k1 = KDF.getInstance(a); + KDF k2 = KDF.getInstance(a, p); + KDF k3 = KDF.getInstance(a, Security.getProvider(p)); + Asserts.assertEquals(k1.getAlgorithm(), k2.getAlgorithm()); + Asserts.assertEquals(k2.getAlgorithm(), k3.getAlgorithm()); + Asserts.assertEquals(k1.getProviderName(), k2.getProviderName()); + Asserts.assertEquals(k2.getProviderName(), k3.getProviderName()); + Asserts.assertEquals(k1.getParameters(), k2.getParameters()); + Asserts.assertEquals(k2.getParameters(), k3.getParameters()); + + // Test KDF getInstance methods with parameters + KDFParameters spec = (KDFParameters) s; + k1 = KDF.getInstance(a, spec); + k2 = KDF.getInstance(a, spec, p); + k3 = KDF.getInstance(a, spec, Security.getProvider(p)); + Asserts.assertEquals(k1.getAlgorithm(), k2.getAlgorithm()); + Asserts.assertEquals(k2.getAlgorithm(), k3.getAlgorithm()); + Asserts.assertEquals(k1.getProviderName(), k2.getProviderName()); + Asserts.assertEquals(k2.getProviderName(), k3.getProviderName()); + Asserts.assertEquals(k1.getParameters(), k2.getParameters()); + Asserts.assertEquals(k2.getParameters(), k3.getParameters()); + }; + + private static final KdfExtractVerifier KdfExtractVerifierImpl = + (ikm, salt) -> { + // ofExtract + HKDFParameterSpec.Builder hkdfParameterSpecBuilder = HKDFParameterSpec.ofExtract(); + addIkmAndSalt(hkdfParameterSpecBuilder, ikm, salt); + + // extractOnly - it is possible to have empty key param so skip when length is 0 + HKDFParameterSpec.Extract parameterSpec = hkdfParameterSpecBuilder.extractOnly(); + checkIKMSaltPresence(ikm, salt, parameterSpec); + + return parameterSpec; + }; + + private static final KdfExpandVerifier KdfExpandVerifierImpl = + (prk, info, len) -> { + // Expand + HKDFParameterSpec.Expand parameterSpec = HKDFParameterSpec.expandOnly(prk, info, len); + + Asserts.assertEqualsByteArray(prk.getEncoded(), parameterSpec.prk().getEncoded()); + Asserts.assertEqualsByteArray(info, parameterSpec.info()); + Asserts.assertEquals(len, parameterSpec.length()); + + return parameterSpec; + }; + + private static final KdfExtThenExpVerifier + KdfExtThenExpVerifierImpl = + (ikm, salt, info, len) -> { + // ofExtract + HKDFParameterSpec.Builder hkdfParameterSpecBuilder = HKDFParameterSpec.ofExtract(); + addIkmAndSalt(hkdfParameterSpecBuilder, ikm, salt); + + // thenExpand + HKDFParameterSpec.ExtractThenExpand parameterSpec = + hkdfParameterSpecBuilder.thenExpand(info, len); + checkIKMSaltPresence(ikm, salt, parameterSpec); + + // Validate info and length + Asserts.assertEqualsByteArray(info, parameterSpec.info()); + Asserts.assertEquals(len, parameterSpec.length()); + + return parameterSpec; + }; + private static final DeriveComparator< + KDF, HKDFParameterSpec, HKDFParameterSpec, String, SecretKey, Integer> + deriveComparatorImpl = + (hk, lhs, rhs, t, s, len) -> { + // deriveKey using two passed in HKDFParameterSpec and compare + byte[] skUsingLhs = hk.deriveKey(t, lhs).getEncoded(); + byte[] skUsingRhs = hk.deriveKey(t, rhs).getEncoded(); + + // compare deriveData and keys using same HKDFParameterSpec are equal + Asserts.assertEqualsByteArray(skUsingLhs, skUsingRhs); + Asserts.assertEqualsByteArray(hk.deriveData(lhs), skUsingLhs); + Asserts.assertEqualsByteArray(hk.deriveData(lhs), skUsingRhs); + Asserts.assertEqualsByteArray(hk.deriveData(lhs), hk.deriveData(rhs)); + + // if 'len < 0' then deriveKey()/deriveData() length check is not required + if (len >= 0) { + Asserts.assertEquals(skUsingLhs.length, len); + } + + // Compare with if SecretKey is passed in parameter + if (s != null) { + Asserts.assertEqualsByteArray(skUsingLhs, s.getEncoded()); + } + }; + // Passed in HKDFParameterSpec returned from different methods and algorithms a1, a2. + // Keys and data derived should be equal. + private static final DeriveVerifier + deriveVerifierImpl = + (hk, lhs, rhs, a1, a2) -> { + SecretKey sk1 = hk.deriveKey(a1, lhs); + SecretKey sk2 = hk.deriveKey(a2, rhs); + Asserts.assertEqualsByteArray(sk1.getEncoded(), sk2.getEncoded()); + + byte[] bk1 = hk.deriveData(lhs); + Asserts.assertEqualsByteArray(bk1, sk1.getEncoded()); + }; + + private static void checkIKMSaltPresence( + Object ikm, Object salt, HKDFParameterSpec parameterSpec) { + final List ikms; + final List salts; + if (parameterSpec instanceof HKDFParameterSpec.Extract) { + ikms = ((HKDFParameterSpec.Extract) parameterSpec).ikms(); + salts = ((HKDFParameterSpec.Extract) parameterSpec).salts(); + } else { // must be HKDFParameterSpec.ExtractThenExpand + ikms = ((HKDFParameterSpec.ExtractThenExpand) parameterSpec).ikms(); + salts = ((HKDFParameterSpec.ExtractThenExpand) parameterSpec).salts(); + } + if ((ikm instanceof SecretKey) || ((byte[]) ikm).length != 0) { + Asserts.assertTrue(ikms.contains(getSecretKey(ikm))); + } + + if ((salt instanceof SecretKey) || ((byte[]) salt).length != 0) { + Asserts.assertTrue(salts.contains(getSecretKey(salt))); + } + } + + private static SecretKey getSecretKey(Object data) { + return (data instanceof SecretKey) + ? (SecretKey) data + : new SecretKeySpec((byte[]) data, "Generic"); + } + + private static void addIkmAndSalt( + HKDFParameterSpec.Builder hkdfParameterSpecBuilder, Object ikm, Object salt) { + if (ikm instanceof SecretKey) { + hkdfParameterSpecBuilder.addIKM((SecretKey) ikm); + } else { + hkdfParameterSpecBuilder.addIKM((byte[]) ikm); + } + + if (salt instanceof SecretKey) { + hkdfParameterSpecBuilder.addSalt((SecretKey) salt); + } else { + hkdfParameterSpecBuilder.addSalt((byte[]) salt); + } + } + + public static void main(String[] args) throws Exception { + System.out.println("Starting Test '" + HKDFExhaustiveTest.class.getName() + "'"); + + // Test KDF.getInstance methods + System.out.println("Testing getInstance methods"); + testGetInstanceMethods(); + testGetInstanceNegative(); + + /* Executing following test cases with one supported algorithm is sufficient */ + KDF hk = KDF.getInstance(KDF_ALGORITHMS[0]); + + // Test extract + System.out.println("Testing extract method"); + testExtractMethod(hk); + + System.out.println("Testing deriveKey and deriveData with extract method"); + testDeriveKeyDataWithExtract(hk); + + // Test expand + System.out.println("Testing expand method"); + testExpandMethod(hk); + + System.out.println("Testing deriveKey and deriveData with expand method"); + testDeriveKeyDataWithExpand(hk); + + // Test ExtractThenExpand + System.out.println("Testing extractThenExpand method"); + testExtractExpandMethod(hk); + + System.out.println("Testing deriveKey and deriveData with extExpand method"); + testDeriveKeyDataWithExtExpand(hk); + + System.out.println("Test executed successfully."); + } + + private static void testGetInstanceMethods() + throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, NoSuchProviderException { + // POSITIVE TestCase: KDF getInstance methods test + for (String algo : KDF_ALGORITHMS) { + KdfGetInstanceVerifier.test(algo, SUNJCE, null); + } + } + + private static void testGetInstanceNegative() { + final String INVALID_STRING = "INVALID"; + final Provider SUNJCE_PROVIDER = Security.getProvider(SUNJCE); + + // getInstance(String algorithm) + Utils.runAndCheckException(() -> KDF.getInstance(null), NullPointerException.class); + Utils.runAndCheckException( + () -> KDF.getInstance(INVALID_STRING), NoSuchAlgorithmException.class); + + // getInstance(String algorithm, String provider) + Utils.runAndCheckException(() -> KDF.getInstance(null, SUNJCE), NullPointerException.class); + Utils.runAndCheckException( + () -> KDF.getInstance(INVALID_STRING, SUNJCE), NoSuchAlgorithmException.class); + Utils.runAndCheckException( + () -> KDF.getInstance(KDF_ALGORITHMS[0], (String) null), NullPointerException.class); + Utils.runAndCheckException( + () -> KDF.getInstance(KDF_ALGORITHMS[0], INVALID_STRING), NoSuchProviderException.class); + + // getInstance(String algorithm, Provider provider) + Utils.runAndCheckException( + () -> KDF.getInstance(null, SUNJCE_PROVIDER), NullPointerException.class); + Utils.runAndCheckException( + () -> KDF.getInstance(INVALID_STRING, SUNJCE_PROVIDER), NoSuchAlgorithmException.class); + Utils.runAndCheckException( + () -> KDF.getInstance(KDF_ALGORITHMS[0], (Provider) null), NullPointerException.class); + + // getInstance(String algorithm, KDFParameters kdfParameters) + // null spec is a valid case but different class is not + Utils.runAndCheckException( + () -> KDF.getInstance(null, (KDFParameters) null), NullPointerException.class); + Utils.runAndCheckException( + () -> KDF.getInstance(INVALID_STRING, (KDFParameters) null), + NoSuchAlgorithmException.class); + + // getInstance(String algorithm, KDFParameters kdfParameters, String provider) + Utils.runAndCheckException( + () -> KDF.getInstance(null, null, SUNJCE), NullPointerException.class); + Utils.runAndCheckException( + () -> KDF.getInstance(INVALID_STRING, null, SUNJCE), NoSuchAlgorithmException.class); + Utils.runAndCheckException( + () -> KDF.getInstance(KDF_ALGORITHMS[0], null, (String) null), NullPointerException.class); + Utils.runAndCheckException( + () -> KDF.getInstance(KDF_ALGORITHMS[0], null, INVALID_STRING), + NoSuchProviderException.class); + + // getInstance(String algorithm, KDFParameters kdfParameters, Provider provider) + Utils.runAndCheckException( + () -> KDF.getInstance(null, null, SUNJCE_PROVIDER), NullPointerException.class); + Utils.runAndCheckException( + () -> KDF.getInstance(INVALID_STRING, null, SUNJCE_PROVIDER), + NoSuchAlgorithmException.class); + Utils.runAndCheckException( + () -> KDF.getInstance(KDF_ALGORITHMS[0], null, (Provider) null), + NullPointerException.class); + } + + private static void testExtractMethod(KDF hk) + throws InvalidAlgorithmParameterException, + InvalidParameterSpecException, + NoSuchAlgorithmException { + List ikmSaltTestData = new ArrayList<>(); + ikmSaltTestData.add(null); + ikmSaltTestData.add(EMPTY); + ikmSaltTestData.add(RAW_DATA.getFirst()); + ikmSaltTestData.add(SECRET_KEY_SPEC_KEYS.getFirst()); + + for (Object ikm : ikmSaltTestData) { + for (Object salt : ikmSaltTestData) { + // NEGATIVE Testcase: expects NullPointerException + if (ikm == null || salt == null) { + Utils.runAndCheckException( + () -> KdfExtractVerifierImpl.extract(ikm, salt), NullPointerException.class); + } else { + // POSITIVE Testcase: Extract - Empty bytes for IKM/SALT + HKDFParameterSpec ext1 = KdfExtractVerifierImpl.extract(ikm, salt); + HKDFParameterSpec ext2 = KdfExtractVerifierImpl.extract(ikm, salt); + deriveComparatorImpl.deriveAndCompare(hk, ext1, ext2, "PRK", null, NEGATIVE_LENGTH); + } + } + } + } + + private static void testDeriveKeyDataWithExtract(KDF hk) + throws InvalidAlgorithmParameterException, + InvalidParameterSpecException, + NoSuchAlgorithmException { + // POSITIVE TestCase: Extract - Derive keys/data with unknown algorithm name + deriveVerifierImpl.derive( + hk, + KdfExtractVerifierImpl.extract(SECRET_KEY_SPEC_KEYS.getFirst(), RAW_DATA.getFirst()), + KdfExtractVerifierImpl.extract(SECRET_KEY_SPEC_KEYS.getFirst(), RAW_DATA.getFirst()), + "XYZ", + "ABC"); + + // NEGATIVE TestCase: Extract - {null, ""} algo to derive key + Utils.runAndCheckException( + () -> + hk.deriveKey( + null, + KdfExtractVerifierImpl.extract( + SECRET_KEY_SPEC_KEYS.getFirst(), RAW_DATA.getFirst())), + NullPointerException.class); + Utils.runAndCheckException( + () -> + hk.deriveKey( + "", + KdfExtractVerifierImpl.extract( + SECRET_KEY_SPEC_KEYS.getFirst(), RAW_DATA.getFirst())), + NoSuchAlgorithmException.class); + } + + private static void testExpandMethod(KDF hk) + throws InvalidAlgorithmParameterException, + InvalidParameterSpecException, + NoSuchAlgorithmException { + SecretKey prk = + hk.deriveKey( + "PRK", + KdfExtractVerifierImpl.extract(SECRET_KEY_SPEC_KEYS.get(1), RAW_DATA.getFirst())); + + // Test extExp with {null, EMPTY} info and {SHORT_LENGTH, LARGE_LENGTH} length + for (byte[] info : new byte[][] {null, EMPTY}) { + for (int length : new Integer[] {SHORT_LENGTH, LARGE_LENGTH}) { + HKDFParameterSpec exp1 = KdfExpandVerifierImpl.expand(prk, info, length); + HKDFParameterSpec exp2 = KdfExpandVerifierImpl.expand(prk, info, length); + deriveComparatorImpl.deriveAndCompare(hk, exp1, exp2, "OKM", null, length); + } + } + + // NEGATIVE TestCase: Expand - PRK=null + Utils.runAndCheckException( + () -> KdfExpandVerifierImpl.expand(null, RAW_DATA.getFirst(), SHORT_LENGTH), + NullPointerException.class); + + // NEGATIVE TestCase: Expand - Derive keys/data of negative length + Utils.runAndCheckException( + () -> + KdfExpandVerifierImpl.expand( + SECRET_KEY_SPEC_KEYS.getFirst(), RAW_DATA.getFirst(), NEGATIVE_LENGTH), + IllegalArgumentException.class); + + // NEGATIVE TestCase: Expand - PRK value too short + Utils.runAndCheckException( + () -> + hk.deriveKey( + "OKM", + KdfExpandVerifierImpl.expand( + new SecretKeySpec(new byte[] {0x00}, "PRK"), null, 32)), + InvalidAlgorithmParameterException.class); + + // NEGATIVE TestCase: Expand - length greater than 255 > hmacLen + Utils.runAndCheckException( + () -> hk.deriveKey("OKM", KdfExpandVerifierImpl.expand(prk, null, 8162)), + InvalidAlgorithmParameterException.class); + } + + private static void testDeriveKeyDataWithExpand(KDF hk) + throws InvalidAlgorithmParameterException, + NoSuchAlgorithmException, + InvalidParameterSpecException { + SecretKey prk = + hk.deriveKey( + "PRK", + KdfExtractVerifierImpl.extract(SECRET_KEY_SPEC_KEYS.get(1), RAW_DATA.getFirst())); + + // POSITIVE TestCase: Expand - Derive keys/data with unknown algorithm name + deriveVerifierImpl.derive( + hk, + KdfExpandVerifierImpl.expand(prk, RAW_DATA.getFirst(), SHORT_LENGTH), + KdfExpandVerifierImpl.expand(prk, RAW_DATA.getFirst(), SHORT_LENGTH), + "XYZ", + "ABC"); + + // NEGATIVE TestCase: Expand - PRK is not derived + Utils.runAndCheckException( + () -> + hk.deriveKey( + "PRK", + KdfExpandVerifierImpl.expand( + SECRET_KEY_SPEC_KEYS.get(1), RAW_DATA.getFirst(), SHORT_LENGTH)), + InvalidAlgorithmParameterException.class); + } + + private static void testExtractExpandMethod(KDF hk) + throws InvalidAlgorithmParameterException, + InvalidParameterSpecException, + NoSuchAlgorithmException { + // Test extExp with {null, EMPTY} info and {SHORT_LENGTH, LARGE_LENGTH} length + for (byte[] info : new byte[][] {null, EMPTY}) { + for (int length : new Integer[] {SHORT_LENGTH, LARGE_LENGTH}) { + HKDFParameterSpec extractExpand1 = + KdfExtThenExpVerifierImpl.extExp( + RAW_DATA.getFirst(), RAW_DATA.getFirst(), info, length); + HKDFParameterSpec extractExpand2 = + KdfExtThenExpVerifierImpl.extExp( + RAW_DATA.getFirst(), RAW_DATA.getFirst(), info, length); + deriveComparatorImpl.deriveAndCompare( + hk, extractExpand1, extractExpand2, "OKM", null, length); + } + } + + // NEGATIVE TestCases: ExtractExpand + List ikmSaltTestData = new ArrayList<>(); + ikmSaltTestData.add(null); + ikmSaltTestData.add(RAW_DATA.getFirst()); + ikmSaltTestData.add(SECRET_KEY_SPEC_KEYS.getFirst()); + + for (Object ikm : ikmSaltTestData) { + for (Object salt : ikmSaltTestData) { + if (ikm == null || salt == null) { + // ikm and/or salt are null, expect NullPointerException + Utils.runAndCheckException( + () -> KdfExtThenExpVerifierImpl.extExp(ikm, salt, RAW_DATA.getFirst(), SHORT_LENGTH), + NullPointerException.class); + } else { + // ikm and salt are not null, test with negative length + Utils.runAndCheckException( + () -> + KdfExtThenExpVerifierImpl.extExp(ikm, salt, RAW_DATA.getFirst(), NEGATIVE_LENGTH), + IllegalArgumentException.class); + } + } + } + + // NEGATIVE TestCase: ExtractThenExpand - length greater than 255 > hmacLen + Utils.runAndCheckException( + () -> hk.deriveKey("OKM", HKDFParameterSpec.ofExtract().thenExpand(null, 8162)), + InvalidAlgorithmParameterException.class); + } + + private static void testDeriveKeyDataWithExtExpand(KDF hk) + throws InvalidAlgorithmParameterException, + InvalidParameterSpecException, + NoSuchAlgorithmException { + // POSITIVE TestCase: ExtractExpand - Derive keys/data with unknown algorithm names + deriveVerifierImpl.derive( + hk, + KdfExtThenExpVerifierImpl.extExp( + SECRET_KEY_SPEC_KEYS.getFirst(), + RAW_DATA.getFirst(), + RAW_DATA.getFirst(), + SHORT_LENGTH), + KdfExtThenExpVerifierImpl.extExp( + SECRET_KEY_SPEC_KEYS.getFirst(), + RAW_DATA.getFirst(), + RAW_DATA.getFirst(), + SHORT_LENGTH), + "XYZ", + "ABC"); + } + + @FunctionalInterface + private interface KdfVerifier { + void test(A a, P p, S s) + throws NoSuchAlgorithmException, + NoSuchProviderException, + InvalidAlgorithmParameterException; + } + + @FunctionalInterface + private interface KdfExtractVerifier { + HKDFParameterSpec extract(K k, S s); + } + + @FunctionalInterface + private interface KdfExpandVerifier { + HKDFParameterSpec expand(P p, I i, L l); + } + + @FunctionalInterface + private interface KdfExtThenExpVerifier { + HKDFParameterSpec extExp(K k, S s, I i, L l); + } + + @FunctionalInterface + private interface DeriveComparator { + void deriveAndCompare(HK hk, L lh, R rh, T t, S s, LN l) + throws InvalidParameterSpecException, + InvalidAlgorithmParameterException, + NoSuchAlgorithmException; + } + + @FunctionalInterface + private interface DeriveVerifier { + void derive(HK hk, L lh, R rh, A1 a1, A2 a2) + throws InvalidParameterSpecException, + InvalidAlgorithmParameterException, + NoSuchAlgorithmException; + } + + private static class KDFAlgorithmParameterSpec implements AlgorithmParameterSpec { + public KDFAlgorithmParameterSpec() {} + } +} diff --git a/test/jdk/com/sun/crypto/provider/KDF/HKDFKnownAnswerTests.java b/test/jdk/com/sun/crypto/provider/KDF/HKDFKnownAnswerTests.java new file mode 100644 index 00000000000..358ffa794fd --- /dev/null +++ b/test/jdk/com/sun/crypto/provider/KDF/HKDFKnownAnswerTests.java @@ -0,0 +1,282 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * 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 8331008 + * @run main HKDFKnownAnswerTests + * @summary Tests for HKDF Expand and Extract Key Derivation Functions + * @enablePreview + */ + +import javax.crypto.KDF; +import javax.crypto.SecretKey; +import javax.crypto.spec.HKDFParameterSpec; +import javax.crypto.spec.SecretKeySpec; +import java.security.InvalidAlgorithmParameterException; +import java.security.Key; +import java.security.NoSuchAlgorithmException; +import java.security.spec.AlgorithmParameterSpec; +import java.security.spec.InvalidParameterSpecException; +import java.util.Arrays; +import java.util.HexFormat; +import java.util.LinkedList; +import java.util.List; +import java.util.Objects; + +public class HKDFKnownAnswerTests { + public static class TestData { + public TestData(String name, String algStr, String ikmStr, + String saltStr, String infoStr, int oLen, + String expPrkStr, + String expOkmStr) { + testName = Objects.requireNonNull(name); + algName = Objects.requireNonNull(algStr); + ikm = HexFormat.of().parseHex(Objects.requireNonNull(ikmStr)); + if ((outLen = oLen) <= 0) { + throw new IllegalArgumentException( + "Output length must be greater than 0"); + } + expectedPRK = HexFormat.of().parseHex(Objects.requireNonNull(expPrkStr)); + expectedOKM = HexFormat.of().parseHex(Objects.requireNonNull(expOkmStr)); + + // Non-mandatory fields - may be null + salt = (saltStr != null) ? HexFormat.of().parseHex(saltStr) : new byte[0]; + info = (infoStr != null) ? HexFormat.of().parseHex(infoStr) : null; + } + + public final String testName; + public final String algName; + public final byte[] ikm; + public final byte[] salt; + public final byte[] info; + public final int outLen; + public final byte[] expectedPRK; + public final byte[] expectedOKM; + } + + public static final List testList = new LinkedList() {{ + add(new TestData("RFC 5869 Test Case 1", "HKDF-SHA256", + "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b", + "000102030405060708090a0b0c", + "f0f1f2f3f4f5f6f7f8f9", + 42, + "077709362c2e32df0ddc3f0dc47bba6390b6c73bb50f9c3122ec844ad7c2b3e5", + "3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c5bf" + + "34007208d5b887185865")); + add(new TestData("RFC 5869 Test Case 2", "HKDF-SHA256", + "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f" + + "202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f" + + "404142434445464748494a4b4c4d4e4f", + "606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f" + + "808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f" + + "a0a1a2a3a4a5a6a7a8a9aaabacadaeaf", + "b0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecf" + + "d0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeef" + + "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff", + 82, + "06a6b88c5853361a06104c9ceb35b45cef760014904671014a193f40c15fc244", + "b11e398dc80327a1c8e7f78c596a49344f012eda2d4efad8a050cc4c19afa97c" + + "59045a99cac7827271cb41c65e590e09da3275600c2f09b8367793a9aca3db71" + + "cc30c58179ec3e87c14c01d5c1f3434f1d87")); + add(new TestData("RFC 5869 Test Case 3", "HKDF-SHA256", + "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b", + new String(new byte[0]), null, 42, + "19ef24a32c717b167f33a91d6f648bdf96596776afdb6377ac434c1c293ccb04", + "8da4e775a563c18f715f802a063c5a31b8a11f5c5ee1879ec3454e5f3c738d2d" + + "9d201395faa4b61a96c8")); + }}; + + public static void main(String args[]) throws Exception { + int testsPassed = 0; + + int testNo = 0; + for (TestData test : testList) { + System.out.println("*** Test " + ++testNo + ": " + + test.testName); + if (runVector(test)) { + testsPassed++; + } + } + + System.out.println("Total tests: " + testList.size() + + ", Passed: " + testsPassed + ", Failed: " + + (testList.size() - testsPassed)); + if (testsPassed != testList.size()) { + throw new RuntimeException("One or more tests failed. " + + "Check output for details"); + } + } + + private static boolean runVector(TestData testData) + throws InvalidParameterSpecException, + InvalidAlgorithmParameterException, + NoSuchAlgorithmException { + String kdfName, prfName; + KDF kdfHkdf, kdfExtract, kdfExpand; + boolean result = true; + SecretKey actualPRK; + SecretKey actualOKM; + byte[] deriveData; + + try { + kdfHkdf = KDF.getInstance(testData.algName); + kdfExtract = KDF.getInstance(testData.algName); + kdfExpand = KDF.getInstance(testData.algName); + } catch (NoSuchAlgorithmException nsae) { + InvalidParameterSpecException exc = + new InvalidParameterSpecException(); + exc.initCause(nsae); + throw exc; + } + + // Set up the input keying material + SecretKey ikmKey = new SecretKeySpec(testData.ikm, "HKDF-IKM"); + + // *** HKDF-Extract-only testing + // Create KDFParameterSpec for the Extract-only operation + AlgorithmParameterSpec derivationSpecExtract = + HKDFParameterSpec.ofExtract().addIKM(ikmKey) + .addSalt(testData.salt) + .extractOnly(); + actualPRK = kdfExtract.deriveKey("Generic", derivationSpecExtract); + + // Re-run the KDF to give us raw output data + deriveData = kdfExtract.deriveData(derivationSpecExtract); + + System.out.println("* HKDF-Extract-Only:"); + result &= compareKeyAndData(actualPRK, deriveData, + testData.expectedPRK); + + // *** HKDF Expand-Only testing + // For these tests, we'll use the actualPRK as the input key + // Create KDFParameterSpec for key output and raw byte output + AlgorithmParameterSpec derivationSpecExpand = HKDFParameterSpec.expandOnly( + actualPRK, testData.info, + testData.outLen); + actualOKM = kdfExpand.deriveKey("Generic", derivationSpecExpand); + + // Re-run the KDF to give us raw output data + deriveData = kdfExpand.deriveData(derivationSpecExpand); + + System.out.println("* HKDF-Expand-Only:"); + result &= compareKeyAndData(actualOKM, deriveData, + testData.expectedOKM); + + // *** HKDF Extract-then-Expand testing + // We can reuse the KDFParameterSpec from the Expand-only test + + // Use the KDF to make us a key + AlgorithmParameterSpec derivationSpecExtractExpand = + HKDFParameterSpec.ofExtract().addIKM(ikmKey) + .addSalt(testData.salt) + .thenExpand(testData.info, + testData.outLen); + actualOKM = kdfHkdf.deriveKey("Generic", derivationSpecExtractExpand); + + // Re-run the KDF to give us raw output data + deriveData = kdfHkdf.deriveData(derivationSpecExtractExpand); + + System.out.println("* HKDF-Extract-then-Expand:"); + result &= compareKeyAndData(actualOKM, deriveData, + testData.expectedOKM); + + return result; + } + + /** + * Compare key-based and data-based productions from the KDF against an + * expected output value. + * + * @param outKey + * the KDF output in key form + * @param outData + * the KDF output as raw bytes + * @param expectedOut + * the expected value + * + * @return true if the underlying data for outKey, outData and expectedOut + * are the same. + */ + private static boolean compareKeyAndData(Key outKey, byte[] outData, + byte[] expectedOut) { + boolean result = true; + + if (Arrays.equals(outKey.getEncoded(), expectedOut)) { + System.out.println("\t* Key output: Pass"); + } else { + result = false; + System.out.println("\t* Key output: FAIL"); + System.out.println("Expected:\n" + + dumpHexBytes(expectedOut, 16, "\n", " ")); + System.out.println("Actual:\n" + + dumpHexBytes(outKey.getEncoded(), 16, "\n", + " ")); + System.out.println(); + } + + if (Arrays.equals(outData, expectedOut)) { + System.out.println("\t* Data output: Pass"); + } else { + result = false; + System.out.println("\t* Data output: FAIL"); + System.out.println("Expected:\n" + + dumpHexBytes(expectedOut, 16, "\n", " ")); + System.out.println("Actual:\n" + + dumpHexBytes(outData, 16, "\n", " ")); + System.out.println(); + } + + return result; + } + + /** + * Dump the hex bytes of a buffer into string form. + * + * @param data + * The array of bytes to dump to stdout. + * @param itemsPerLine + * The number of bytes to display per line if the {@code lineDelim} + * character is blank then all bytes will be printed on a single line. + * @param lineDelim + * The delimiter between lines + * @param itemDelim + * The delimiter between bytes + * + * @return The hexdump of the byte array + */ + private static String dumpHexBytes(byte[] data, int itemsPerLine, + String lineDelim, String itemDelim) { + StringBuilder sb = new StringBuilder(); + if (data != null) { + for (int i = 0; i < data.length; i++) { + if (i % itemsPerLine == 0 && i != 0) { + sb.append(lineDelim); + } + sb.append(String.format("%02X", data[i])).append(itemDelim); + } + } + + return sb.toString(); + } +} diff --git a/test/jdk/com/sun/crypto/provider/KDF/HKDFSaltIKMTest.java b/test/jdk/com/sun/crypto/provider/KDF/HKDFSaltIKMTest.java new file mode 100644 index 00000000000..1cd0feab614 --- /dev/null +++ b/test/jdk/com/sun/crypto/provider/KDF/HKDFSaltIKMTest.java @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * 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 8331008 + * @summary addIKM and addSalt consistency checks + * @library /test/lib + * @enablePreview + */ + +import jdk.test.lib.Asserts; +import jdk.test.lib.security.SeededSecureRandom; + +import javax.crypto.KDF; +import javax.crypto.spec.HKDFParameterSpec; +import javax.crypto.spec.SecretKeySpec; +import java.io.ByteArrayOutputStream; +import java.util.Arrays; + +public class HKDFSaltIKMTest { + static String[] NAMES = {"HKDF-SHA256", "HKDF-SHA384", "HKDF-SHA512"}; + public static void main(String[] args) throws Exception { + var r = SeededSecureRandom.one(); + var atlast = 0; + KDF kdf = null; + var alg = ""; + for (var i = 0; i < 1_000_000; i++) { + if (kdf == null || r.nextBoolean()) { + alg = NAMES[r.nextInt(3)]; + kdf = KDF.getInstance(alg); // randomly recreate KDF object + } + var b = HKDFParameterSpec.ofExtract(); + var salts = new ByteArrayOutputStream(); // accumulate salt fragments + var ikms = new ByteArrayOutputStream(); // accumulate ikm fragments + while (r.nextBoolean()) { + if (r.nextBoolean()) { + var ikm = r.nBytes(r.nextInt(10)); + if (r.nextBoolean() && ikm.length > 0) { + b.addIKM(new SecretKeySpec(ikm, "X")); + } else { + b.addIKM(ikm); + } + ikms.writeBytes(ikm); + } else { + var salt = r.nBytes(r.nextInt(10)); + if (r.nextBoolean() && salt.length > 0) { + b.addSalt(new SecretKeySpec(salt, "X")); + } else { + b.addSalt(salt); + } + salts.writeBytes(salt); + } + } + var info = r.nextBoolean() ? null : r.nBytes(r.nextInt(100)); + var l = r.nextInt(200) + 1; + var kdf2 = r.nextBoolean() ? kdf : KDF.getInstance(alg); + var k1 = kdf2.deriveData(HKDFParameterSpec.ofExtract().addIKM(ikms.toByteArray()) + .addSalt(salts.toByteArray()).thenExpand(info, l)); + atlast = Arrays.hashCode(k1) + 17 * atlast; + if (r.nextBoolean()) { + var k2 = kdf.deriveData(b.thenExpand(info, l)); + Asserts.assertEqualsByteArray(k1, k2); + } else { + var prk = kdf.deriveKey("PRK", b.extractOnly()); + var k2 = kdf.deriveData(HKDFParameterSpec.expandOnly(prk, info, l)); + Asserts.assertEqualsByteArray(k1, k2); + } + } + System.out.println(atlast); + } +} \ No newline at end of file diff --git a/test/jdk/javax/crypto/KDF/KDFDelayedProviderSyncTest.java b/test/jdk/javax/crypto/KDF/KDFDelayedProviderSyncTest.java new file mode 100644 index 00000000000..9a9da43b544 --- /dev/null +++ b/test/jdk/javax/crypto/KDF/KDFDelayedProviderSyncTest.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * 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 8331008 + * @library /test/lib + * @run testng KDFDelayedProviderSyncTest + * @summary multi-threading test for KDF + * @enablePreview + */ + +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +import javax.crypto.KDF; +import javax.crypto.SecretKey; +import javax.crypto.spec.HKDFParameterSpec; +import java.math.BigInteger; +import java.security.InvalidAlgorithmParameterException; +import java.security.NoSuchAlgorithmException; +import java.security.spec.AlgorithmParameterSpec; +import java.util.HexFormat; + +public class KDFDelayedProviderSyncTest { + KDF kdfUnderTest; + byte[] ikm = new BigInteger("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b", + 16).toByteArray(); + byte[] salt = new BigInteger("000102030405060708090a0b0c", + 16).toByteArray(); + byte[] info = new BigInteger("f0f1f2f3f4f5f6f7f8f9", 16).toByteArray(); + AlgorithmParameterSpec derivationSpec = + HKDFParameterSpec.ofExtract().addIKM(ikm).addSalt(salt).thenExpand( + info, 42); + String expectedResult = + "666b33562ebc5e2f041774192e0534efca06f82a5fca17ec8c6ae1b9f5466adba1d77d06480567ddd2d1"; + + @BeforeClass + public void setUp() throws NoSuchAlgorithmException { + kdfUnderTest = KDF.getInstance("HKDF-SHA256"); + } + + @Test(threadPoolSize = 50, invocationCount = 100, timeOut = 150) + public void testDerive() + throws InvalidAlgorithmParameterException, NoSuchAlgorithmException { + SecretKey result = kdfUnderTest.deriveKey("Generic", derivationSpec); + assert (HexFormat.of().formatHex(result.getEncoded()).equals( + expectedResult)); + + byte[] resultData = kdfUnderTest.deriveData(derivationSpec); + assert (HexFormat.of().formatHex(resultData).equals(expectedResult)); + } +} diff --git a/test/jdk/javax/crypto/KDF/KDFDelayedProviderTest.java b/test/jdk/javax/crypto/KDF/KDFDelayedProviderTest.java new file mode 100644 index 00000000000..edebae217f2 --- /dev/null +++ b/test/jdk/javax/crypto/KDF/KDFDelayedProviderTest.java @@ -0,0 +1,180 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * 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 8331008 + * @library /test/lib /test/jdk/security/unsignedjce + * @build java.base/javax.crypto.ProviderVerifier + * @run main/othervm KDFDelayedProviderTest + * @summary delayed provider selection + * @enablePreview + */ + +import jdk.test.lib.Asserts; + +import javax.crypto.KDF; +import javax.crypto.KDFSpi; +import javax.crypto.SecretKey; +import java.security.InvalidAlgorithmParameterException; +import javax.crypto.KDFParameters; +import java.security.NoSuchAlgorithmException; +import java.security.Provider; +import java.security.Security; +import java.security.spec.AlgorithmParameterSpec; +import java.util.Objects; + +public class KDFDelayedProviderTest { + public static void main(String[] args) throws Exception { + Security.addProvider(new Provider1()); + Security.addProvider(new Provider2()); + Security.addProvider(new Provider3()); + KDF kdf; + + kdf = KDF.getInstance("X", new KDFParameters() {}); + kdf.deriveData(new AlgorithmParameterSpec() {}); + Asserts.assertEquals(kdf.getProviderName(), "P1"); + + kdf = KDF.getInstance("X"); + kdf.deriveData(new MyDerivationSpec() {}); + Asserts.assertEquals(kdf.getProviderName(), "P2"); + + kdf = KDF.getInstance("X"); + kdf.deriveData(new AlgorithmParameterSpec() {}); + Asserts.assertEquals(kdf.getProviderName(), "P3"); + + boolean thrown = true; + try { + kdf = KDF.getInstance("Y"); + thrown = false; + } catch(Exception nsae) { + // Expected exception + Asserts.assertTrue(nsae instanceof NoSuchAlgorithmException); + System.out.println("Expected NoSuchAlgorithmException"); + } + Asserts.assertTrue(thrown); + + thrown = true; + try { + kdf = KDF.getInstance("HKDF-SHA256", new MyKDFParameters()); + thrown = false; + } catch (Exception iape) { + // Expected exception + Asserts.assertTrue(iape instanceof InvalidAlgorithmParameterException); + System.out.println("Expected InvalidAlgorithmParameterException"); + } + Asserts.assertTrue(thrown); + + thrown = true; + try { + kdf = KDF.getInstance("HKDF-SHA256"); + kdf.deriveData(new MyDerivationSpec()); + thrown = false; + } catch (Exception iape) { + // Expected exception + Asserts.assertTrue(iape instanceof InvalidAlgorithmParameterException); + System.out.println("Expected InvalidAlgorithmParameterException"); + } + Asserts.assertTrue(thrown); + } + + public static class Provider1 extends Provider { + public Provider1() { + super("P1", "1", "1"); + put("KDF.X", KDF1.class.getName()); + } + } + + // KDF1 requires a params at getInstance() + public static class KDF1 extends KDF0 { + public KDF1(KDFParameters e) throws InvalidAlgorithmParameterException { + super(Objects.requireNonNull(e)); + } + } + + public static class Provider2 extends Provider { + public Provider2() { + super("P2", "1", "1"); + put("KDF.X", KDF2.class.getName()); + } + } + + // KDF2 requires input to be a specific type + public static class KDF2 extends KDF0 { + public KDF2(KDFParameters e) + throws InvalidAlgorithmParameterException { + super(null); + } + + @Override + protected byte[] engineDeriveData( + AlgorithmParameterSpec derivationSpec) + throws InvalidAlgorithmParameterException { + if (derivationSpec instanceof MyDerivationSpec) { + return null; + } else { + throw new InvalidAlgorithmParameterException(); + } + } + } + + public static class Provider3 extends Provider { + public Provider3() { + super("P3", "1", "1"); + put("KDF.X", KDF3.class.getName()); + } + } + + // KDF3 doesn't care about anything + public static class KDF3 extends KDF0 { + public KDF3(KDFParameters e) throws InvalidAlgorithmParameterException { + super(null); + } + } + + public abstract static class KDF0 extends KDFSpi { + public KDF0(KDFParameters a) throws InvalidAlgorithmParameterException { + super(a); + } + + protected SecretKey engineDeriveKey(String alg, + AlgorithmParameterSpec derivationSpec) + throws InvalidAlgorithmParameterException { + return null; + } + + protected byte[] engineDeriveData( + AlgorithmParameterSpec derivationSpec) + throws InvalidAlgorithmParameterException { + return new byte[0]; + } + + protected KDFParameters engineGetParameters(){ + return null; + } + } + + static class MyDerivationSpec implements AlgorithmParameterSpec {} + + static class MyKDFParameters implements KDFParameters {} +} diff --git a/test/jdk/javax/crypto/KDF/KDFDelayedProviderThreadingTest.java b/test/jdk/javax/crypto/KDF/KDFDelayedProviderThreadingTest.java new file mode 100644 index 00000000000..7b91badef95 --- /dev/null +++ b/test/jdk/javax/crypto/KDF/KDFDelayedProviderThreadingTest.java @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * 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 8331008 + * @library /test/lib /test/jdk/security/unsignedjce + * @build java.base/javax.crypto.ProviderVerifier + * @run main/othervm KDFDelayedProviderThreadingTest + * @summary delayed provider selection threading test + * @enablePreview + */ + +import jdk.test.lib.Asserts; + +import javax.crypto.KDF; +import javax.crypto.KDFParameters; +import javax.crypto.KDFSpi; +import javax.crypto.SecretKey; +import javax.crypto.spec.HKDFParameterSpec; +import java.security.InvalidAlgorithmParameterException; +import java.security.Provider; +import java.security.Security; +import java.security.spec.AlgorithmParameterSpec; +import java.util.Arrays; + +public class KDFDelayedProviderThreadingTest { + /// This number of iterations is enough to see a case where the threads + /// arrange themselves such that both `deriveData` attempts cause "ERROR", + /// which is still a passing case. + static final int ITERATIONS = 10000; + static int threadOrderReversalCounter = 0; + static final String ERROR = "ERROR"; + static volatile String out; + static final HKDFParameterSpec input + = HKDFParameterSpec.ofExtract().extractOnly(); + + static String derive(KDF kdf) { + try { + return Arrays.toString(kdf.deriveData(input)); + } catch (Exception e) { + return ERROR; + } + } + + public static void main(String[] args) throws Exception { + Security.insertProviderAt(new P(), 1); + for (int i = 0; i < ITERATIONS; i++) { + test(); + } + + // If the value of threadOrderReversalCounter is consistently zero, + // then this test may need to be adjusted for newer hardware to ensure + // a thorough test. This didn't seem fitting for a check, such as + // `Asserts.assertTrue(threadOrderReversalCounter > 0);`, since we + // may not want to start failing the test right away when running on + // better hardware someday. + System.out.println("Also tested atypical threading condition " + + threadOrderReversalCounter + "/" + ITERATIONS + + " iterations (depends on hardware specs/utilization)."); + } + + static void test() throws Exception { + var k = KDF.getInstance("HKDF-SHA256"); + var t1 = new Thread(() -> out = derive(k)); + var t2 = new Thread(() -> k.getProviderName()); + t1.start(); + t2.start(); + t1.join(); + t2.join(); + + String out2 = derive(k); + Asserts.assertEquals(out, out2); + if (out.length() < 10) { // "error" + threadOrderReversalCounter++; + } + } + + public static class P extends Provider { + public P() { + super("ME", "1", "ME"); + put("KDF.HKDF-SHA256", K.class.getName()); + } + } + + public static class K extends KDFSpi { + + public K(KDFParameters p) throws InvalidAlgorithmParameterException { + super(p); + } + + @Override + protected KDFParameters engineGetParameters() { + return null; + } + + @Override + protected SecretKey engineDeriveKey(String alg, + AlgorithmParameterSpec derivationSpec) + throws InvalidAlgorithmParameterException { + throw new InvalidAlgorithmParameterException(); + } + + @Override + protected byte[] engineDeriveData(AlgorithmParameterSpec derivationSpec) + throws InvalidAlgorithmParameterException { + throw new InvalidAlgorithmParameterException(); + } + } +} \ No newline at end of file diff --git a/test/jdk/security/unsignedjce/java.base/javax/crypto/ProviderVerifier.java b/test/jdk/security/unsignedjce/java.base/javax/crypto/ProviderVerifier.java new file mode 100644 index 00000000000..03e3e9904cc --- /dev/null +++ b/test/jdk/security/unsignedjce/java.base/javax/crypto/ProviderVerifier.java @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2007, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * 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 javax.crypto; + +import java.io.IOException; +import java.net.URL; +import java.security.Provider; + +/** + * This class is included here to enable testing of Delayed Provider Selection + * by certain KDF tests. It only stubs out the necessary methods. + * + * @since 24 + */ +final class ProviderVerifier { + + private final CryptoPermissions appPerms = null; + + /** + * Creates a {@code ProviderVerifier} object to verify the given URL. + * + * @param jarURL the JAR file to be verified. + * @param savePerms if {@code true}, save the permissions allowed by the + * exemption mechanism + */ + ProviderVerifier(URL jarURL, boolean savePerms) { + this(jarURL, null, savePerms); + } + + /** + * Creates a {@code ProviderVerifier} object to verify the given URL. + * + * @param jarURL the JAR file to be verified + * @param provider the corresponding provider. + * @param savePerms if {@code true}, save the permissions allowed by the + * exemption mechanism + */ + ProviderVerifier(URL jarURL, Provider provider, boolean savePerms) { + // The URL for the JAR file we want to verify. + } + + /** + * Only a stub is needed for the Delayed Provider Selection test. + */ + void verify() throws IOException { return; } + + /** + * Verify that the provided certs include the + * framework signing certificate. + * + * @param certs the list of certs to be checked. + * @throws Exception if the list of certs did not contain + * the framework signing certificate + */ + static void verifyPolicySigned(java.security.cert.Certificate[] certs) + throws Exception { + } + + /** + * Returns {@code true} if the given provider is JDK trusted crypto provider + * if the implementation supports fast-path verification. + */ + static boolean isTrustedCryptoProvider(Provider provider) { + return false; + } + + /** + * Returns the permissions which are bundled with the JAR file, + * aka the "cryptoperms" file. + *

+ * NOTE: if this {@code ProviderVerifier} instance is constructed + * with "savePerms" equal to {@code false}, then this method would always + * return {@code null}. + */ + CryptoPermissions getPermissions() { + return appPerms; + } +}