mirror of
https://github.com/openjdk/jdk.git
synced 2025-08-26 22:34:27 +02:00
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:
parent
847cc5ebac
commit
2a1ae0ff89
17 changed files with 3321 additions and 1 deletions
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
*/
|
||||
|
|
|
@ -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);
|
||||
|
|
681
src/java.base/share/classes/javax/crypto/KDF.java
Normal file
681
src/java.base/share/classes/javax/crypto/KDF.java
Normal 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);
|
||||
}
|
||||
}
|
50
src/java.base/share/classes/javax/crypto/KDFParameters.java
Normal file
50
src/java.base/share/classes/javax/crypto/KDFParameters.java
Normal 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 {}
|
157
src/java.base/share/classes/javax/crypto/KDFSpi.java
Normal file
157
src/java.base/share/classes/javax/crypto/KDFSpi.java
Normal 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;
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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.
|
||||
|
|
|
@ -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();
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue