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