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