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

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

View file

@ -0,0 +1,414 @@
/*
* Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.crypto.provider;
import javax.crypto.KDFSpi;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.ShortBufferException;
import javax.crypto.spec.HKDFParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.ByteArrayOutputStream;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import javax.crypto.KDFParameters;
import java.security.NoSuchAlgorithmException;
import java.security.ProviderException;
import java.security.spec.AlgorithmParameterSpec;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* KDF implementation for the HKDF function.
* <p>
* This class implements the HKDF-Extract and HKDF-Expand functions from RFC
* 5869. This implementation provides the complete Extract-then-Expand HKDF
* function as well as Extract-only and Expand-only variants.
*
* @spec https://www.rfc-editor.org/info/rfc5869
* RFC 5869: HMAC-based Extract-and-Expand Key Derivation Function (HKDF)
*/
abstract class HKDFKeyDerivation extends KDFSpi {
private final int hmacLen;
private final String hmacAlgName;
private enum SupportedHmac {
SHA256("HmacSHA256", 32),
SHA384("HmacSHA384", 48),
SHA512("HmacSHA512", 64);
private final String hmacAlg;
private final int hmacLen;
SupportedHmac(String hmacAlg, int hmacLen) {
this.hmacAlg = hmacAlg;
this.hmacLen = hmacLen;
}
};
/**
* The sole constructor.
*
* @param kdfParameters
* the initialization parameters (may be {@code null})
*
* @throws InvalidAlgorithmParameterException
* if the initialization parameters are inappropriate for this
* {@code KDFSpi}
*/
private HKDFKeyDerivation(SupportedHmac supportedHmac,
KDFParameters kdfParameters)
throws InvalidAlgorithmParameterException {
super(kdfParameters);
if (kdfParameters != null) {
throw new InvalidAlgorithmParameterException(
supportedHmac.hmacAlg + " does not support parameters");
}
this.hmacAlgName = supportedHmac.hmacAlg;
this.hmacLen = supportedHmac.hmacLen;
}
/**
* Derive a key, returned as a {@code SecretKey} object.
*
* @return a derived {@code SecretKey} object of the specified algorithm
*
* @throws InvalidAlgorithmParameterException
* if the information contained within the {@code derivationSpec} is
* invalid or if the combination of {@code alg} and the
* {@code derivationSpec} results in something invalid
* @throws NoSuchAlgorithmException
* if {@code alg} is empty
* @throws NullPointerException
* if {@code alg} is {@code null}
*/
@Override
protected SecretKey engineDeriveKey(String alg,
AlgorithmParameterSpec derivationSpec)
throws InvalidAlgorithmParameterException,
NoSuchAlgorithmException {
if (alg == null) {
throw new NullPointerException(
"the algorithm for the SecretKey return value must not be"
+ " null");
}
if (alg.isEmpty()) {
throw new NoSuchAlgorithmException(
"the algorithm for the SecretKey return value must not be "
+ "empty");
}
return new SecretKeySpec(engineDeriveData(derivationSpec), alg);
}
/**
* Obtain raw data from a key derivation function.
*
* @return a derived {@code byte[]}
*
* @throws InvalidAlgorithmParameterException
* if the information contained within the {@code KDFParameterSpec}
* is invalid or incorrect for the type of key to be derived
* @throws UnsupportedOperationException
* if the derived keying material is not extractable
*/
@Override
protected byte[] engineDeriveData(AlgorithmParameterSpec derivationSpec)
throws InvalidAlgorithmParameterException {
List<SecretKey> ikms, salts;
byte[] inputKeyMaterial, salt, pseudoRandomKey, info;
int length;
if (derivationSpec instanceof HKDFParameterSpec.Extract anExtract) {
ikms = anExtract.ikms();
salts = anExtract.salts();
// we should be able to combine both of the above Lists of key
// segments into one SecretKey object each, unless we were passed
// something bogus or an unexportable P11 key
inputKeyMaterial = null;
salt = null;
try {
inputKeyMaterial = consolidateKeyMaterial(ikms);
salt = consolidateKeyMaterial(salts);
// perform extract
return hkdfExtract(inputKeyMaterial, salt);
} catch (InvalidKeyException ike) {
throw new InvalidAlgorithmParameterException(
"an HKDF Extract could not be initialized with the "
+ "given key or salt material", ike);
} catch (NoSuchAlgorithmException nsae) {
// This is bubbling up from the getInstance of the Mac/Hmac.
// Since we're defining these values internally, it is unlikely.
throw new ProviderException(
"could not instantiate a Mac with the provided "
+ "algorithm",
nsae);
} finally {
if (inputKeyMaterial != null) {
Arrays.fill(inputKeyMaterial, (byte) 0x00);
}
if (salt != null) {
Arrays.fill(salt, (byte) 0x00);
}
}
} else if (derivationSpec instanceof HKDFParameterSpec.Expand anExpand) {
// set this value in the "if"
if ((pseudoRandomKey = anExpand.prk().getEncoded()) == null) {
throw new AssertionError(
"PRK is required for HKDFParameterSpec.Expand");
}
// set this value in the "if"
if ((info = anExpand.info()) == null) {
info = new byte[0];
}
length = anExpand.length();
if (length > (hmacLen * 255)) {
throw new InvalidAlgorithmParameterException(
"Requested length exceeds maximum allowed length");
}
// perform expand
try {
return hkdfExpand(pseudoRandomKey, info, length);
} catch (InvalidKeyException ike) {
throw new InvalidAlgorithmParameterException(
"an HKDF Expand could not be initialized with the "
+ "given keying material", ike);
} catch (NoSuchAlgorithmException nsae) {
// This is bubbling up from the getInstance of the Mac/Hmac.
// Since we're defining these values internally, it is unlikely.
throw new ProviderException(
"could not instantiate a Mac with the provided "
+ "algorithm",
nsae);
} finally {
Arrays.fill(pseudoRandomKey, (byte) 0x00);
}
} else if (derivationSpec instanceof HKDFParameterSpec.ExtractThenExpand anExtractThenExpand) {
ikms = anExtractThenExpand.ikms();
salts = anExtractThenExpand.salts();
// we should be able to combine both of the above Lists of key
// segments into one SecretKey object each, unless we were passed
// something bogus or an unexportable P11 key
inputKeyMaterial = null;
salt = null;
pseudoRandomKey = null;
try {
inputKeyMaterial = consolidateKeyMaterial(ikms);
salt = consolidateKeyMaterial(salts);
// set this value in the "if"
if ((info = anExtractThenExpand.info()) == null) {
info = new byte[0];
}
length = anExtractThenExpand.length();
if (length > (hmacLen * 255)) {
throw new InvalidAlgorithmParameterException(
"Requested length exceeds maximum allowed length");
}
// perform extract and then expand
pseudoRandomKey = hkdfExtract(inputKeyMaterial, salt);
return hkdfExpand(pseudoRandomKey, info, length);
} catch (InvalidKeyException ike) {
throw new InvalidAlgorithmParameterException(
"an HKDF ExtractThenExpand could not be initialized "
+ "with the given key or salt material", ike);
} catch (NoSuchAlgorithmException nsae) {
// This is bubbling up from the getInstance of the Mac/HMAC.
// Since we're defining these values internally, it is unlikely.
throw new ProviderException(
"could not instantiate a Mac with the provided "
+ "algorithm",
nsae);
} finally {
if (inputKeyMaterial != null) {
Arrays.fill(inputKeyMaterial, (byte) 0x00);
}
if (salt != null) {
Arrays.fill(salt, (byte) 0x00);
}
if (pseudoRandomKey != null) {
Arrays.fill(pseudoRandomKey, (byte) 0x00);
}
}
}
throw new InvalidAlgorithmParameterException(
"an HKDF derivation requires a valid HKDFParameterSpec");
}
// throws an InvalidKeyException if any key is unextractable
private byte[] consolidateKeyMaterial(List<SecretKey> keys)
throws InvalidKeyException {
if (keys != null && !keys.isEmpty()) {
ArrayList<SecretKey> localKeys = new ArrayList<>(keys);
if (localKeys.size() == 1) {
// return this element
SecretKey checkIt = localKeys.get(0);
return CipherCore.getKeyBytes(checkIt);
} else {
ByteArrayOutputStream os = new ByteArrayOutputStream();
for (SecretKey workItem : localKeys) {
os.writeBytes(CipherCore.getKeyBytes(workItem));
}
// deliberately omitting os.flush(), since we are writing to
// memory, and toByteArray() reads like there isn't an explicit
// need for this call
return os.toByteArray();
}
} else if (keys != null) {
return new byte[0];
} else {
throw new InvalidKeyException(
"List of key segments could not be consolidated");
}
}
/**
* Perform the HKDF-Extract operation.
*
* @param inputKeyMaterial
* the input keying material used for the HKDF-Extract operation.
* @param salt
* the salt value used for HKDF-Extract
*
* @return a byte array containing the pseudorandom key (PRK)
*
* @throws InvalidKeyException
* if an invalid salt was provided through the
* {@code HKDFParameterSpec}
*/
private byte[] hkdfExtract(byte[] inputKeyMaterial, byte[] salt)
throws InvalidKeyException, NoSuchAlgorithmException {
// salt will not be null
if (salt.length == 0) {
salt = new byte[hmacLen];
}
Mac hmacObj = Mac.getInstance(hmacAlgName);
hmacObj.init(new SecretKeySpec(salt, hmacAlgName));
// inputKeyMaterial will not be null
return hmacObj.doFinal(inputKeyMaterial);
}
/**
* Perform the HKDF-Expand operation.
*
* @param prk
* the pseudorandom key used for HKDF-Expand
* @param info
* optional context and application specific information or
* {@code null} if no info data is provided.
* @param outLen
* the length in bytes of the required output
*
* @return a byte array containing the complete {@code KDF} output. This
* will be at least as long as the requested length in the
* {@code outLen} parameter, but will be rounded up to the nearest
* multiple of the HMAC output length.
*
* @throws InvalidKeyException
* if an invalid PRK was provided through the
* {@code HKDFParameterSpec} or derived during the extract phase.
*/
private byte[] hkdfExpand(byte[] prk, byte[] info, int outLen)
throws InvalidKeyException, NoSuchAlgorithmException {
byte[] kdfOutput;
if (prk == null || prk.length < hmacLen) {
throw new InvalidKeyException(
"prk must be at least " + hmacLen + " bytes");
}
SecretKey pseudoRandomKey = new SecretKeySpec(prk, hmacAlgName);
Mac hmacObj = Mac.getInstance(hmacAlgName);
// Calculate the number of rounds of HMAC that are needed to
// meet the requested data. Then set up the buffers we will need.
hmacObj.init(pseudoRandomKey);
int rounds = (outLen + hmacLen - 1) / hmacLen;
kdfOutput = new byte[outLen];
int i = 0;
int offset = 0;
try {
while (i < rounds) {
if (i > 0) {
hmacObj.update(kdfOutput, offset - hmacLen,
hmacLen); // add T(i-1)
}
hmacObj.update(info); // Add info
hmacObj.update((byte) ++i); // Add round number
if (i == rounds && (outLen - offset < hmacLen)) {
// special handling for last chunk
byte[] tmp = hmacObj.doFinal();
System.arraycopy(tmp, 0, kdfOutput, offset,
outLen - offset);
Arrays.fill(tmp, (byte) 0x00);
offset = outLen;
} else {
hmacObj.doFinal(kdfOutput, offset);
offset += hmacLen;
}
}
} catch (ShortBufferException sbe) {
// This really shouldn't happen given that we've
// sized the buffers to their largest possible size up-front,
// but just in case...
throw new ProviderException(sbe);
}
return kdfOutput;
}
protected KDFParameters engineGetParameters() {
return null;
}
public static final class HKDFSHA256 extends HKDFKeyDerivation {
public HKDFSHA256(KDFParameters kdfParameters)
throws InvalidAlgorithmParameterException {
super(SupportedHmac.SHA256, kdfParameters);
}
}
public static final class HKDFSHA384 extends HKDFKeyDerivation {
public HKDFSHA384(KDFParameters kdfParameters)
throws InvalidAlgorithmParameterException {
super(SupportedHmac.SHA384, kdfParameters);
}
}
public static final class HKDFSHA512 extends HKDFKeyDerivation {
public HKDFSHA512(KDFParameters kdfParameters)
throws InvalidAlgorithmParameterException {
super(SupportedHmac.SHA512, kdfParameters);
}
}
}

View file

@ -457,6 +457,16 @@ public final class SunJCE extends Provider {
"com.sun.crypto.provider.DHKeyAgreement",
attrs);
/*
* Key Derivation engines
*/
ps("KDF", "HKDF-SHA256",
"com.sun.crypto.provider.HKDFKeyDerivation$HKDFSHA256");
ps("KDF", "HKDF-SHA384",
"com.sun.crypto.provider.HKDFKeyDerivation$HKDFSHA384");
ps("KDF", "HKDF-SHA512",
"com.sun.crypto.provider.HKDFKeyDerivation$HKDFSHA512");
/*
* Algorithm Parameter engines
*/