8331008: Implement JEP 478: Key Derivation Function API (Preview)

Co-authored-by: Rajan Halade <rhalade@openjdk.org>
Co-authored-by: Weijun Wang <weijun@openjdk.org>
Co-authored-by: Valerie Peng <valeriep@openjdk.org>
Reviewed-by: weijun, valeriep
This commit is contained in:
Kevin Driver 2024-11-05 21:07:52 +00:00
parent 847cc5ebac
commit 2a1ae0ff89
17 changed files with 3321 additions and 1 deletions

View File

@ -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.
* <p>
* 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<SecretKey> 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<SecretKey> keys)
throws InvalidKeyException {
if (keys != null && !keys.isEmpty()) {
ArrayList<SecretKey> 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);
}
}
}

View File

@ -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
*/

View File

@ -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);

View File

@ -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.
* <p>
* {@code KDF} objects are instantiated with the {@code getInstance} family of
* methods.
* <p>
* 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.
* <p>
* 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);
*}
* <br>
* <h2><a id="ConcurrentAccess">Concurrent Access</a></h2>
* 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.
* <br>
* <h2><a id="DelayedProviderSelection">Delayed Provider Selection</a></h2>
* 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
* <i>delayed provider selection</i>. 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.
* <p>
* 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<Service> 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<Service> 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 <a href="#DelayedProviderSelection">Delayed Provider
* Selection</a>
*/
public String getProviderName() {
useFirstSpi();
return theOne.provider().getName();
}
/**
* Returns the {@code KDFParameters} used with this {@code KDF} object.
* <p>
* 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 <a href="#DelayedProviderSelection">Delayed Provider
* Selection</a>
*/
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 <a href="{@docRoot}/../specs/security/standard-names.html#kdf-algorithms">
* Java Security Standard Algorithm Names Specification</a> 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 <a href="#DelayedProviderSelection">Delayed Provider
* Selection</a>
*/
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 <a href="{@docRoot}/../specs/security/standard-names.html#kdf-algorithms">
* Java Security Standard Algorithm Names Specification</a> 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 <a href="{@docRoot}/../specs/security/standard-names.html#kdf-algorithms">
* Java Security Standard Algorithm Names Specification</a> 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 <a href="{@docRoot}/../specs/security/standard-names.html#kdf-algorithms">
* Java Security Standard Algorithm Names Specification</a> 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 <a href="#DelayedProviderSelection">Delayed Provider
* Selection</a>
*/
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<Service> 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 <a href="{@docRoot}/../specs/security/standard-names.html#kdf-algorithms">
* Java Security Standard Algorithm Names Specification</a> 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 <a href="{@docRoot}/../specs/security/standard-names.html#kdf-algorithms">
* Java Security Standard Algorithm Names Specification</a> 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 <a href="#DelayedProviderSelection">Delayed Provider
* Selection</a>
*
*/
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 <a href="#DelayedProviderSelection">Delayed Provider
* Selection</a>
*
*/
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.
* <p>
* 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<Service> 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);
}
}

View File

@ -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.
* <p>
* 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.
* <p>
* When supplied, the
* {@link KDF#getInstance(String, KDFParameters) KDF.getInstance} methods return
* a {@code KDF} that is initialized with the specified parameters.
* <p>
* 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 {}

View File

@ -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 <i>Service Provider Interface</i> (<b>SPI</b>) for the
* Key Derivation Function ({@link KDF}) class.
* <p>
* 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.
* <p>
* 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.
* <p>
* 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.
* <p>
* 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.
* <p>
* 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.
* <p>
* 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;
}

View File

@ -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 <a href="http://tools.ietf.org/html/rfc5869">RFC
* 5869</a>.
* <p>
* 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.
* <p>
* 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.
* <p>
* 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.
* <p>
* 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<SecretKey> ikms = new ArrayList<>();
private List<SecretKey> 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.
* <p>
* 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.
* <p>
* 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.
* <p>
* 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.
* <p>
* 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 <a
* href="http://tools.ietf.org/html/rfc5869">RFC 5869</a>.
*/
@PreviewFeature(feature = PreviewFeature.Feature.KEY_DERIVATION)
final class Extract implements HKDFParameterSpec {
// HKDF-Extract(salt, IKM) -> PRK
private final List<SecretKey> ikms;
private final List<SecretKey> salts;
private Extract(List<SecretKey> ikms, List<SecretKey> 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.
* <p>
* 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<SecretKey> 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.
* <p>
* 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<SecretKey> salts() {
return salts;
}
}
/**
* Defines the input parameters of an Expand operation as defined in <a
* href="http://tools.ietf.org/html/rfc5869">RFC 5869</a>.
*/
@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 <a href="http://tools.ietf.org/html/rfc5869">RFC 5869</a>.
*/
@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.
* <p>
* 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<SecretKey> 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.
* <p>
* 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<SecretKey> 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();
}
}
}

View File

@ -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.

View File

@ -139,7 +139,7 @@ public class Debug {
System.err.println("engine=<engines>");
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();

View File

@ -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)));
}
}

View File

@ -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<SecretKey> SECRET_KEY_SPEC_KEYS =
List.of(
new SecretKeySpec(new byte[] {0}, "HKDF-IKM"),
new SecretKeySpec("IKM".getBytes(), "HKDF-IKM"));
private static final List<byte[]> 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<String, String, AlgorithmParameterSpec> 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<Object, Object> 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<SecretKey, byte[], Integer> 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<Object, Object, byte[], Integer>
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<KDF, HKDFParameterSpec, HKDFParameterSpec, String, String>
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<SecretKey> ikms;
final List<SecretKey> 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<Object> 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<Object> 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<A, P, S> {
void test(A a, P p, S s)
throws NoSuchAlgorithmException,
NoSuchProviderException,
InvalidAlgorithmParameterException;
}
@FunctionalInterface
private interface KdfExtractVerifier<K, S> {
HKDFParameterSpec extract(K k, S s);
}
@FunctionalInterface
private interface KdfExpandVerifier<P, I, L> {
HKDFParameterSpec expand(P p, I i, L l);
}
@FunctionalInterface
private interface KdfExtThenExpVerifier<K, S, I, L> {
HKDFParameterSpec extExp(K k, S s, I i, L l);
}
@FunctionalInterface
private interface DeriveComparator<HK, L, R, T, S, LN> {
void deriveAndCompare(HK hk, L lh, R rh, T t, S s, LN l)
throws InvalidParameterSpecException,
InvalidAlgorithmParameterException,
NoSuchAlgorithmException;
}
@FunctionalInterface
private interface DeriveVerifier<HK, L, R, A1, A2> {
void derive(HK hk, L lh, R rh, A1 a1, A2 a2)
throws InvalidParameterSpecException,
InvalidAlgorithmParameterException,
NoSuchAlgorithmException;
}
private static class KDFAlgorithmParameterSpec implements AlgorithmParameterSpec {
public KDFAlgorithmParameterSpec() {}
}
}

View File

@ -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<TestData> testList = new LinkedList<TestData>() {{
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();
}
}

View File

@ -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);
}
}

View File

@ -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));
}
}

View File

@ -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 {}
}

View File

@ -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();
}
}
}

View File

@ -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.
* <p>
* 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;
}
}