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",
|
"com.sun.crypto.provider.DHKeyAgreement",
|
||||||
attrs);
|
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
|
* Algorithm Parameter engines
|
||||||
*/
|
*/
|
||||||
|
@ -27,6 +27,7 @@ package java.security;
|
|||||||
|
|
||||||
import jdk.internal.event.SecurityProviderServiceEvent;
|
import jdk.internal.event.SecurityProviderServiceEvent;
|
||||||
|
|
||||||
|
import javax.crypto.KDFParameters;
|
||||||
import javax.security.auth.login.Configuration;
|
import javax.security.auth.login.Configuration;
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.security.cert.CertStoreParameters;
|
import java.security.cert.CertStoreParameters;
|
||||||
@ -1604,6 +1605,7 @@ public abstract class Provider extends Properties {
|
|||||||
addEngine("KeyGenerator", false, null);
|
addEngine("KeyGenerator", false, null);
|
||||||
addEngine("SecretKeyFactory", false, null);
|
addEngine("SecretKeyFactory", false, null);
|
||||||
addEngine("KEM", true, null);
|
addEngine("KEM", true, null);
|
||||||
|
addEngine("KDF", false, KDFParameters.class);
|
||||||
// JSSE
|
// JSSE
|
||||||
addEngine("KeyManagerFactory", false, null);
|
addEngine("KeyManagerFactory", false, null);
|
||||||
addEngine("SSLContext", 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,
|
STREAM_GATHERERS,
|
||||||
@JEP(number=476, title="Module Import Declarations", status="Preview")
|
@JEP(number=476, title="Module Import Declarations", status="Preview")
|
||||||
MODULE_IMPORTS,
|
MODULE_IMPORTS,
|
||||||
|
@JEP(number=478, title="Key Derivation Function API", status="Preview")
|
||||||
|
KEY_DERIVATION,
|
||||||
LANGUAGE_MODEL,
|
LANGUAGE_MODEL,
|
||||||
/**
|
/**
|
||||||
* A key for testing.
|
* A key for testing.
|
||||||
|
@ -139,7 +139,7 @@ public class Debug {
|
|||||||
System.err.println("engine=<engines>");
|
System.err.println("engine=<engines>");
|
||||||
System.err.println(" only dump output for the specified list");
|
System.err.println(" only dump output for the specified list");
|
||||||
System.err.println(" of JCA engines. Supported values:");
|
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(" KeyPairGenerator, KeyStore, Mac,");
|
||||||
System.err.println(" MessageDigest, SecureRandom, Signature.");
|
System.err.println(" MessageDigest, SecureRandom, Signature.");
|
||||||
System.err.println();
|
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…
x
Reference in New Issue
Block a user