8328119: Support HKDF in SunPKCS11 (Preview)

8346720: Support Generic keys in SunPKCS11 SecretKeyFactory

Co-authored-by: Francisco Ferrari Bihurriet <fferrari@openjdk.org>
Co-authored-by: Martin Balao <mbalao@openjdk.org>
Reviewed-by: valeriep, kdriver, weijun
This commit is contained in:
Martin Balao 2025-02-13 16:55:33 +00:00
parent 28e744dc64
commit 6ddbcc34c0
16 changed files with 1582 additions and 66 deletions

View file

@ -156,6 +156,7 @@ module java.base {
java.compiler, java.compiler,
java.desktop, // for ScopedValue java.desktop, // for ScopedValue
jdk.compiler, jdk.compiler,
jdk.crypto.cryptoki, // participates in preview features
jdk.incubator.vector, // participates in preview features jdk.incubator.vector, // participates in preview features
jdk.jartool, // participates in preview features jdk.jartool, // participates in preview features
jdk.jdeps, // participates in preview features jdk.jdeps, // participates in preview features

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2014, 2023, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2014, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -23,6 +23,8 @@
* questions. * questions.
*/ */
import jdk.internal.javac.ParticipatesInPreview;
/** /**
* Provides the implementation of the SunPKCS11 security provider. * Provides the implementation of the SunPKCS11 security provider.
* *
@ -31,6 +33,7 @@
* @moduleGraph * @moduleGraph
* @since 9 * @since 9
*/ */
@ParticipatesInPreview
module jdk.crypto.cryptoki { module jdk.crypto.cryptoki {
provides java.security.Provider with sun.security.pkcs11.SunPKCS11; provides java.security.Provider with sun.security.pkcs11.SunPKCS11;
} }

View file

@ -0,0 +1,367 @@
/*
* Copyright (c) 2025, Red Hat, Inc.
* 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 sun.security.pkcs11;
import javax.crypto.KDFParameters;
import javax.crypto.KDFSpi;
import javax.crypto.SecretKey;
import javax.crypto.spec.HKDFParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.*;
import java.security.spec.*;
import java.util.Arrays;
import java.util.List;
import static sun.security.pkcs11.TemplateManager.*;
import sun.security.pkcs11.wrapper.*;
import static sun.security.pkcs11.wrapper.PKCS11Constants.*;
final class P11HKDF extends KDFSpi {
private final Token token;
private final P11SecretKeyFactory.HKDFKeyInfo svcKi;
private static final SecretKey EMPTY_KEY = new SecretKey() {
@Override
public String getAlgorithm() {
return "Generic";
}
@Override
public String getFormat() {
return "RAW";
}
@Override
public byte[] getEncoded() {
return new byte[0];
}
};
private static KDFParameters requireNull(KDFParameters kdfParameters,
String message) throws InvalidAlgorithmParameterException {
if (kdfParameters != null) {
throw new InvalidAlgorithmParameterException(message);
}
return null;
}
private void checkMechanismEnabled(long mechanism) {
if (!token.provider.config.isEnabled(mechanism)) {
throw new ProviderException("Mechanism " +
Functions.getMechanismName(mechanism) +
" is disabled through 'enabledMechanisms' or " +
"'disabledMechanisms' in " + token.provider.getName() +
" configuration.");
}
}
P11HKDF(Token token, String algorithm, KDFParameters kdfParameters)
throws InvalidAlgorithmParameterException {
super(requireNull(kdfParameters,
algorithm + " does not support parameters"));
this.token = token;
this.svcKi = P11SecretKeyFactory.getHKDFKeyInfo(algorithm);
assert this.svcKi != null : "Unsupported HKDF algorithm " + algorithm;
}
@Override
protected KDFParameters engineGetParameters() {
return 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 derive(alg, derivationSpec, SecretKey.class);
}
@Override
protected byte[] engineDeriveData(AlgorithmParameterSpec derivationSpec)
throws InvalidAlgorithmParameterException {
return derive("Generic", derivationSpec, byte[].class);
}
private <T> T derive(String alg, AlgorithmParameterSpec derivationSpec,
Class<T> retType) throws InvalidAlgorithmParameterException {
SecretKey baseKey;
SecretKey salt = EMPTY_KEY;
byte[] info = null;
int outLen;
boolean isExtract = false, isExpand = false;
boolean isData = retType == byte[].class;
assert isData || retType == SecretKey.class : "Invalid return type.";
assert alg != null : "The algorithm cannot be null.";
long mechanism = isData ? CKM_HKDF_DATA : CKM_HKDF_DERIVE;
checkMechanismEnabled(mechanism);
switch (derivationSpec) {
case HKDFParameterSpec.Extract anExtract -> {
isExtract = true;
baseKey = consolidateKeyMaterial(anExtract.ikms());
salt = consolidateKeyMaterial(anExtract.salts());
outLen = svcKi.prkLen / 8;
assert outLen * 8 == svcKi.prkLen : "Invalid PRK length.";
}
case HKDFParameterSpec.Expand anExpand -> {
isExpand = true;
baseKey = anExpand.prk();
outLen = anExpand.length();
info = anExpand.info();
}
case HKDFParameterSpec.ExtractThenExpand anExtractExpand -> {
isExtract = true;
isExpand = true;
baseKey = consolidateKeyMaterial(anExtractExpand.ikms());
salt = consolidateKeyMaterial(anExtractExpand.salts());
outLen = anExtractExpand.length();
info = anExtractExpand.info();
}
case null -> throw new NullPointerException(
"derivationSpec must be a " + HKDFParameterSpec.class +
" instance, instead of null.");
default -> throw new InvalidAlgorithmParameterException(
"derivationSpec must be a " + HKDFParameterSpec.class +
" instance, instead of " + derivationSpec.getClass());
}
P11Key p11BaseKey = convertKey(baseKey, (isExtract ? "IKM" : "PRK") +
" could not be converted to a token key for HKDF derivation.");
long saltType = CKF_HKDF_SALT_NULL;
byte[] saltBytes = null;
P11Key p11SaltKey = null;
if (salt instanceof SecretKeySpec) {
saltType = CKF_HKDF_SALT_DATA;
saltBytes = salt.getEncoded();
} else if (salt != EMPTY_KEY) {
// consolidateKeyMaterial returns a salt from the token.
saltType = CKF_HKDF_SALT_KEY;
p11SaltKey = (P11Key.P11SecretKey) salt;
assert p11SaltKey.token == token : "salt must be from the same " +
"token as service.";
}
P11SecretKeyFactory.KeyInfo ki = P11SecretKeyFactory.getKeyInfo(alg);
if (ki == null) {
throw new InvalidAlgorithmParameterException("A PKCS #11 key " +
"type (CKK_*) was not found for a key of the algorithm '" +
alg + "'.");
}
long derivedKeyType = ki.keyType;
long derivedKeyClass = isData ? CKO_DATA : CKO_SECRET_KEY;
CK_ATTRIBUTE[] attrs = new CK_ATTRIBUTE[] {
new CK_ATTRIBUTE(CKA_CLASS, derivedKeyClass),
new CK_ATTRIBUTE(CKA_KEY_TYPE, derivedKeyType),
new CK_ATTRIBUTE(CKA_VALUE_LEN, outLen)
};
Session session = null;
long baseKeyID = p11BaseKey.getKeyID();
try {
session = token.getOpSession();
CK_HKDF_PARAMS params = new CK_HKDF_PARAMS(isExtract, isExpand,
svcKi.hmacMech, saltType, saltBytes, p11SaltKey != null ?
p11SaltKey.getKeyID() : 0L, info);
attrs = token.getAttributes(O_GENERATE, derivedKeyClass,
derivedKeyType, attrs);
long derivedObjectID = token.p11.C_DeriveKey(session.id(),
new CK_MECHANISM(mechanism, params), baseKeyID, attrs);
Object ret;
if (isData) {
try {
CK_ATTRIBUTE[] dataAttr = new CK_ATTRIBUTE[] {
new CK_ATTRIBUTE(CKA_VALUE)
};
token.p11.C_GetAttributeValue(session.id(), derivedObjectID,
dataAttr);
ret = dataAttr[0].getByteArray();
} finally {
token.p11.C_DestroyObject(session.id(), derivedObjectID);
}
} else {
ret = P11Key.secretKey(session, derivedObjectID, alg,
outLen * 8, null);
}
return retType.cast(ret);
} catch (PKCS11Exception e) {
throw new ProviderException("HKDF derivation for algorithm '" +
alg + "' failed.", e);
} finally {
if (p11SaltKey != null) {
p11SaltKey.releaseKeyID();
}
p11BaseKey.releaseKeyID();
token.releaseSession(session);
}
}
private P11Key.P11SecretKey convertKey(SecretKey key, String errorMessage) {
try {
return (P11Key.P11SecretKey) P11SecretKeyFactory.convertKey(token,
key, null);
} catch (InvalidKeyException ike) {
throw new ProviderException(errorMessage, ike);
}
}
private abstract sealed class KeyMaterialMerger permits
AnyKeyMaterialMerger, KeyKeyMaterialMerger, DataKeyMaterialMerger {
final KeyMaterialMerger merge(SecretKey nextKeyMaterial) {
if (nextKeyMaterial instanceof SecretKeySpec) {
return merge(nextKeyMaterial.getEncoded());
} else {
return merge(convertKey(nextKeyMaterial,
"Failure when merging key material."));
}
}
abstract SecretKey getKeyMaterial();
protected abstract KeyMaterialMerger merge(byte[] nextKeyMaterial);
protected abstract KeyMaterialMerger merge(
P11Key.P11SecretKey nextKeyMaterial);
protected final P11Key.P11SecretKey p11Merge(
P11Key.P11SecretKey baseKey, CK_MECHANISM ckMech,
int derivedKeyLen) {
checkMechanismEnabled(ckMech.mechanism);
Session session = null;
long baseKeyID = baseKey.getKeyID();
try {
session = token.getOpSession();
CK_ATTRIBUTE[] attrs = new CK_ATTRIBUTE[] {
new CK_ATTRIBUTE(CKA_CLASS, CKO_SECRET_KEY),
new CK_ATTRIBUTE(CKA_KEY_TYPE, CKK_GENERIC_SECRET),
};
long derivedKeyID = token.p11.C_DeriveKey(session.id(), ckMech,
baseKeyID, attrs);
return (P11Key.P11SecretKey) P11Key.secretKey(session,
derivedKeyID, "Generic", derivedKeyLen * 8, null);
} catch (PKCS11Exception e) {
throw new ProviderException("Failure when merging key " +
"material.", e);
} finally {
baseKey.releaseKeyID();
token.releaseSession(session);
}
}
}
private final class AnyKeyMaterialMerger extends KeyMaterialMerger {
protected KeyMaterialMerger merge(byte[] nextKeyMaterial) {
return P11HKDF.this.new DataKeyMaterialMerger(nextKeyMaterial);
}
protected KeyMaterialMerger merge(P11Key.P11SecretKey nextKeyMaterial) {
return P11HKDF.this.new KeyKeyMaterialMerger(nextKeyMaterial);
}
SecretKey getKeyMaterial() {
return EMPTY_KEY;
}
}
private final class KeyKeyMaterialMerger extends KeyMaterialMerger {
private P11Key.P11SecretKey keyMaterial;
KeyKeyMaterialMerger(P11Key.P11SecretKey keyMaterial) {
this.keyMaterial = keyMaterial;
}
protected KeyMaterialMerger merge(byte[] nextKeyMaterial) {
keyMaterial = p11Merge(keyMaterial,
new CK_MECHANISM(CKM_CONCATENATE_BASE_AND_DATA,
new CK_KEY_DERIVATION_STRING_DATA(nextKeyMaterial)),
keyMaterial.keyLength + nextKeyMaterial.length);
return this;
}
protected KeyMaterialMerger merge(P11Key.P11SecretKey nextKeyMaterial) {
try {
keyMaterial = p11Merge(keyMaterial,
new CK_MECHANISM(CKM_CONCATENATE_BASE_AND_KEY,
nextKeyMaterial.getKeyID()),
keyMaterial.keyLength + nextKeyMaterial.keyLength);
} finally {
nextKeyMaterial.releaseKeyID();
}
return this;
}
SecretKey getKeyMaterial() {
return keyMaterial;
}
}
private final class DataKeyMaterialMerger extends KeyMaterialMerger {
private byte[] keyMaterial;
DataKeyMaterialMerger(byte[] keyMaterial) {
this.keyMaterial = keyMaterial;
}
protected KeyMaterialMerger merge(byte[] nextKeyMaterial) {
keyMaterial = Arrays.copyOf(keyMaterial,
keyMaterial.length + nextKeyMaterial.length);
System.arraycopy(nextKeyMaterial, 0, keyMaterial,
keyMaterial.length - nextKeyMaterial.length,
nextKeyMaterial.length);
return this;
}
protected KeyMaterialMerger merge(P11Key.P11SecretKey nextKeyMaterial) {
return P11HKDF.this.new KeyKeyMaterialMerger(p11Merge(
nextKeyMaterial, new CK_MECHANISM(
CKM_CONCATENATE_DATA_AND_BASE,
new CK_KEY_DERIVATION_STRING_DATA(keyMaterial)),
keyMaterial.length + nextKeyMaterial.keyLength));
}
SecretKey getKeyMaterial() {
return new SecretKeySpec(keyMaterial, "Generic");
}
}
private SecretKey consolidateKeyMaterial(List<SecretKey> keys) {
KeyMaterialMerger keyMerger = P11HKDF.this.new AnyKeyMaterialMerger();
for (SecretKey key : keys) {
keyMerger = keyMerger.merge(key);
}
return keyMerger.getKeyMaterial();
}
}

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2003, 2024, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2003, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -446,7 +446,7 @@ abstract class P11Key implements Key, Length {
} }
} }
private static class P11SecretKey extends P11Key implements SecretKey { static class P11SecretKey extends P11Key implements SecretKey {
@Serial @Serial
private static final long serialVersionUID = -7828241727014329084L; private static final long serialVersionUID = -7828241727014329084L;

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2003, 2023, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2003, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -90,31 +90,24 @@ final class P11Mac extends MacSpi {
// one byte buffer for the update(byte) method, initialized on demand // one byte buffer for the update(byte) method, initialized on demand
private byte[] oneByte; private byte[] oneByte;
P11Mac(Token token, String algorithm, long mechanism) P11Mac(Token token, String algorithm, long mechanism) {
throws PKCS11Exception {
super(); super();
this.token = token; this.token = token;
this.algorithm = algorithm; this.algorithm = algorithm;
this.svcPbeKi = P11SecretKeyFactory.getPBEKeyInfo(algorithm); this.svcPbeKi = P11SecretKeyFactory.getPBEKeyInfo(algorithm);
Long params = null; if (svcPbeKi != null) {
macLength = switch ((int) mechanism) { macLength = svcPbeKi.keyLen / 8;
case (int) CKM_MD5_HMAC -> 16; } else {
case (int) CKM_SHA_1_HMAC -> 20; P11SecretKeyFactory.HMACKeyInfo svcKi =
case (int) CKM_SHA224_HMAC, (int) CKM_SHA512_224_HMAC, (int) CKM_SHA3_224_HMAC -> 28; P11SecretKeyFactory.getHMACKeyInfo(algorithm);
case (int) CKM_SHA256_HMAC, (int) CKM_SHA512_256_HMAC, (int) CKM_SHA3_256_HMAC -> 32; if (svcKi == null) {
case (int) CKM_SHA384_HMAC, (int) CKM_SHA3_384_HMAC -> 48; throw new ProviderException("Unknown mechanism: " + mechanism);
case (int) CKM_SHA512_HMAC, (int) CKM_SHA3_512_HMAC -> 64;
case (int) CKM_SSL3_MD5_MAC -> {
params = Long.valueOf(16);
yield 16;
} }
case (int) CKM_SSL3_SHA1_MAC -> { macLength = svcKi.keyLen / 8;
params = Long.valueOf(20); }
yield 20; ckMechanism = new CK_MECHANISM(mechanism, mechanism == CKM_SSL3_MD5_MAC
} || mechanism == CKM_SSL3_SHA1_MAC ? Long.valueOf(macLength) :
default -> throw new ProviderException("Unknown mechanism: " + mechanism); null);
};
ckMechanism = new CK_MECHANISM(mechanism, params);
} }
// reset the states to the pre-initialized values // reset the states to the pre-initialized values

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2003, 2024, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2003, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -67,21 +67,12 @@ final class P11SecretKeyFactory extends SecretKeyFactorySpi {
} }
private static final Map<String, KeyInfo> keyInfo = new HashMap<>(); private static final Map<String, KeyInfo> keyInfo = new HashMap<>();
private static final KeyInfo HMAC = new KeyInfo("HMAC", PCKK_HMAC);
private static final KeyInfo SSLMAC = new KeyInfo("SSLMAC", PCKK_SSLMAC);
static KeyInfo getKeyInfo(String algo) { static KeyInfo getKeyInfo(String algo) {
KeyInfo ki = keyInfo.get(algo); KeyInfo ki = keyInfo.get(algo);
if (ki == null) { if (ki == null) {
String algoUpper = algo.toUpperCase(Locale.ENGLISH); String algoUpper = algo.toUpperCase(Locale.ENGLISH);
ki = keyInfo.get(algoUpper); ki = keyInfo.get(algoUpper);
if (ki == null) {
if (algoUpper.startsWith("HMAC")) {
return HMAC;
} else if (algoUpper.startsWith("SSLMAC")) {
return SSLMAC;
}
}
} }
return ki; return ki;
} }
@ -93,12 +84,26 @@ final class P11SecretKeyFactory extends SecretKeyFactorySpi {
return null; return null;
} }
static HMACKeyInfo getHMACKeyInfo(String algo) {
if (getKeyInfo(algo) instanceof HMACKeyInfo hmacKi) {
return hmacKi;
}
return null;
}
static HKDFKeyInfo getHKDFKeyInfo(String algo) {
if (getKeyInfo(algo) instanceof HKDFKeyInfo hkdfKi) {
return hkdfKi;
}
return null;
}
private static void putKeyInfo(KeyInfo ki) { private static void putKeyInfo(KeyInfo ki) {
keyInfo.put(ki.algo, ki); keyInfo.put(ki.algo, ki);
keyInfo.put(ki.algo.toUpperCase(Locale.ENGLISH), ki); keyInfo.put(ki.algo.toUpperCase(Locale.ENGLISH), ki);
} }
static sealed class KeyInfo permits PBEKeyInfo { static sealed class KeyInfo permits PBEKeyInfo, HMACKeyInfo, HKDFKeyInfo {
public final String algo; public final String algo;
public final long keyType; public final long keyType;
@ -129,6 +134,29 @@ final class P11SecretKeyFactory extends SecretKeyFactorySpi {
} }
} }
static final class HMACKeyInfo extends KeyInfo {
public final long mech;
public final int keyLen;
HMACKeyInfo(String algo, long mech, int keyLen) {
super(algo, CKK_GENERIC_SECRET);
this.mech = mech;
this.keyLen = keyLen;
}
}
static final class HKDFKeyInfo extends KeyInfo {
public static final long UNKNOWN_KEY_TYPE = -1;
public final long hmacMech;
public final int prkLen;
HKDFKeyInfo(String algo, HMACKeyInfo hmacKi) {
super(algo, UNKNOWN_KEY_TYPE);
hmacMech = hmacKi.mech;
prkLen = hmacKi.keyLen;
}
}
abstract static sealed class PBEKeyInfo extends KeyInfo abstract static sealed class PBEKeyInfo extends KeyInfo
permits AESPBEKeyInfo, PBKDF2KeyInfo, P12MacPBEKeyInfo { permits AESPBEKeyInfo, PBKDF2KeyInfo, P12MacPBEKeyInfo {
public static final long INVALID_PRF = -1; public static final long INVALID_PRF = -1;
@ -169,9 +197,9 @@ final class P11SecretKeyFactory extends SecretKeyFactorySpi {
private static final CK_ATTRIBUTE[] attr = new CK_ATTRIBUTE[] { private static final CK_ATTRIBUTE[] attr = new CK_ATTRIBUTE[] {
CK_ATTRIBUTE.SIGN_TRUE}; CK_ATTRIBUTE.SIGN_TRUE};
P12MacPBEKeyInfo(String algo, long kdfMech, int keyLen) { P12MacPBEKeyInfo(String algo, long kdfMech, HMACKeyInfo hmacKi) {
super(algo, kdfMech, PBEKeyInfo.INVALID_PRF, super(algo, kdfMech, PBEKeyInfo.INVALID_PRF,
CKK_GENERIC_SECRET, keyLen, attr); CKK_GENERIC_SECRET, hmacKi.keyLen, attr);
} }
} }
@ -195,6 +223,36 @@ final class P11SecretKeyFactory extends SecretKeyFactorySpi {
putKeyInfo(new KeyInfo("TlsMasterSecret", PCKK_TLSMASTER)); putKeyInfo(new KeyInfo("TlsMasterSecret", PCKK_TLSMASTER));
putKeyInfo(new KeyInfo("Generic", CKK_GENERIC_SECRET)); putKeyInfo(new KeyInfo("Generic", CKK_GENERIC_SECRET));
HMACKeyInfo hmacSHA1 =
new HMACKeyInfo("HmacSHA1", CKM_SHA_1_HMAC, 160);
HMACKeyInfo hmacSHA224 =
new HMACKeyInfo("HmacSHA224", CKM_SHA224_HMAC, 224);
HMACKeyInfo hmacSHA256 =
new HMACKeyInfo("HmacSHA256", CKM_SHA256_HMAC, 256);
HMACKeyInfo hmacSHA384 =
new HMACKeyInfo("HmacSHA384", CKM_SHA384_HMAC, 384);
HMACKeyInfo hmacSHA512 =
new HMACKeyInfo("HmacSHA512", CKM_SHA512_HMAC, 512);
putKeyInfo(hmacSHA1);
putKeyInfo(hmacSHA224);
putKeyInfo(hmacSHA256);
putKeyInfo(hmacSHA384);
putKeyInfo(hmacSHA512);
putKeyInfo(new HMACKeyInfo("HmacMD5", CKM_MD5_HMAC, 128));
putKeyInfo(new HMACKeyInfo("HmacSHA512/224", CKM_SHA512_224_HMAC, 224));
putKeyInfo(new HMACKeyInfo("HmacSHA3-224", CKM_SHA3_224_HMAC, 224));
putKeyInfo(new HMACKeyInfo("HmacSHA512/256", CKM_SHA512_256_HMAC, 256));
putKeyInfo(new HMACKeyInfo("HmacSHA3-256", CKM_SHA3_256_HMAC, 256));
putKeyInfo(new HMACKeyInfo("HmacSHA3-384", CKM_SHA3_384_HMAC, 384));
putKeyInfo(new HMACKeyInfo("HmacSHA3-512", CKM_SHA3_512_HMAC, 512));
putKeyInfo(new HMACKeyInfo("SslMacMD5", CKM_SSL3_MD5_MAC, 128));
putKeyInfo(new HMACKeyInfo("SslMacSHA1", CKM_SSL3_SHA1_MAC, 160));
putKeyInfo(new HKDFKeyInfo("HKDF-SHA256", hmacSHA256));
putKeyInfo(new HKDFKeyInfo("HKDF-SHA384", hmacSHA384));
putKeyInfo(new HKDFKeyInfo("HKDF-SHA512", hmacSHA512));
putKeyInfo(new AESPBEKeyInfo("PBEWithHmacSHA1AndAES_128", putKeyInfo(new AESPBEKeyInfo("PBEWithHmacSHA1AndAES_128",
CKP_PKCS5_PBKD2_HMAC_SHA1, 128)); CKP_PKCS5_PBKD2_HMAC_SHA1, 128));
putKeyInfo(new AESPBEKeyInfo("PBEWithHmacSHA224AndAES_128", putKeyInfo(new AESPBEKeyInfo("PBEWithHmacSHA224AndAES_128",
@ -228,15 +286,15 @@ final class P11SecretKeyFactory extends SecretKeyFactorySpi {
CKP_PKCS5_PBKD2_HMAC_SHA512)); CKP_PKCS5_PBKD2_HMAC_SHA512));
putKeyInfo(new P12MacPBEKeyInfo("HmacPBESHA1", putKeyInfo(new P12MacPBEKeyInfo("HmacPBESHA1",
CKM_PBA_SHA1_WITH_SHA1_HMAC, 160)); CKM_PBA_SHA1_WITH_SHA1_HMAC, hmacSHA1));
putKeyInfo(new P12MacPBEKeyInfo("HmacPBESHA224", putKeyInfo(new P12MacPBEKeyInfo("HmacPBESHA224",
CKM_NSS_PKCS12_PBE_SHA224_HMAC_KEY_GEN, 224)); CKM_NSS_PKCS12_PBE_SHA224_HMAC_KEY_GEN, hmacSHA224));
putKeyInfo(new P12MacPBEKeyInfo("HmacPBESHA256", putKeyInfo(new P12MacPBEKeyInfo("HmacPBESHA256",
CKM_NSS_PKCS12_PBE_SHA256_HMAC_KEY_GEN, 256)); CKM_NSS_PKCS12_PBE_SHA256_HMAC_KEY_GEN, hmacSHA256));
putKeyInfo(new P12MacPBEKeyInfo("HmacPBESHA384", putKeyInfo(new P12MacPBEKeyInfo("HmacPBESHA384",
CKM_NSS_PKCS12_PBE_SHA384_HMAC_KEY_GEN, 384)); CKM_NSS_PKCS12_PBE_SHA384_HMAC_KEY_GEN, hmacSHA384));
putKeyInfo(new P12MacPBEKeyInfo("HmacPBESHA512", putKeyInfo(new P12MacPBEKeyInfo("HmacPBESHA512",
CKM_NSS_PKCS12_PBE_SHA512_HMAC_KEY_GEN, 512)); CKM_NSS_PKCS12_PBE_SHA512_HMAC_KEY_GEN, hmacSHA512));
} }
// No pseudo key types // No pseudo key types
@ -287,8 +345,7 @@ final class P11SecretKeyFactory extends SecretKeyFactorySpi {
} }
// Check if the key can be used for the service. // Check if the key can be used for the service.
// Any key can be used for a MAC service. // Any key can be used for a MAC service.
if (svcAlgo != keyAlgo && if (svcAlgo != keyAlgo && !(si instanceof HMACKeyInfo)) {
si.keyType != PCKK_HMAC && si.keyType != PCKK_SSLMAC) {
ki = getKeyInfo(keyAlgo); ki = getKeyInfo(keyAlgo);
if (ki == null || !KeyInfo.checkUse(ki, si)) { if (ki == null || !KeyInfo.checkUse(ki, si)) {
throw new InvalidKeyException("Cannot use a " + keyAlgo + throw new InvalidKeyException("Cannot use a " + keyAlgo +
@ -348,8 +405,7 @@ final class P11SecretKeyFactory extends SecretKeyFactorySpi {
} }
byte[] encoded = key.getEncoded(); byte[] encoded = key.getEncoded();
try { try {
p11Key = createKey(token, encoded, svcAlgo, si.keyType, p11Key = createKey(token, encoded, svcAlgo, si, extraAttrs);
extraAttrs);
} finally { } finally {
Arrays.fill(encoded, (byte) 0); Arrays.fill(encoded, (byte) 0);
} }
@ -486,10 +542,11 @@ final class P11SecretKeyFactory extends SecretKeyFactorySpi {
} }
private static P11Key createKey(Token token, byte[] encoded, private static P11Key createKey(Token token, byte[] encoded,
String algorithm, long keyType, CK_ATTRIBUTE[] extraAttrs) String algorithm, KeyInfo ki, CK_ATTRIBUTE[] extraAttrs)
throws InvalidKeyException { throws InvalidKeyException {
int n = encoded.length << 3; int n = encoded.length << 3;
int keyLength = n; int keyLength = n;
long keyType = ki.keyType;
try { try {
switch ((int) keyType) { switch ((int) keyType) {
case (int) CKK_DES -> { case (int) CKK_DES -> {
@ -520,16 +577,12 @@ final class P11SecretKeyFactory extends SecretKeyFactorySpi {
CKM_CHACHA20_KEY_GEN, n, token); CKM_CHACHA20_KEY_GEN, n, token);
case (int) CKK_GENERIC_SECRET, (int) PCKK_TLSPREMASTER, (int) PCKK_TLSRSAPREMASTER, (int) PCKK_TLSMASTER -> case (int) CKK_GENERIC_SECRET, (int) PCKK_TLSPREMASTER, (int) PCKK_TLSRSAPREMASTER, (int) PCKK_TLSMASTER ->
keyType = CKK_GENERIC_SECRET; keyType = CKK_GENERIC_SECRET;
case (int) PCKK_SSLMAC, (int) PCKK_HMAC -> {
if (n == 0) {
throw new InvalidKeyException
("MAC keys must not be empty");
}
keyType = CKK_GENERIC_SECRET;
}
default -> throw new InvalidKeyException("Unknown algorithm " + default -> throw new InvalidKeyException("Unknown algorithm " +
algorithm); algorithm);
} }
if (ki instanceof HMACKeyInfo && n == 0) {
throw new InvalidKeyException("MAC keys must not be empty");
}
} catch (InvalidAlgorithmParameterException iape) { } catch (InvalidAlgorithmParameterException iape) {
throw new InvalidKeyException("Invalid key for " + algorithm, throw new InvalidKeyException("Invalid key for " + algorithm,
iape); iape);

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2003, 2024, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2003, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -31,6 +31,7 @@ import java.util.stream.Collectors;
import java.security.*; import java.security.*;
import java.security.interfaces.*; import java.security.interfaces.*;
import javax.crypto.KDFParameters;
import javax.crypto.interfaces.*; import javax.crypto.interfaces.*;
import javax.security.auth.Subject; import javax.security.auth.Subject;
@ -526,6 +527,8 @@ public final class SunPKCS11 extends AuthProvider {
private static final String SR = "SecureRandom"; private static final String SR = "SecureRandom";
private static final String KDF = "KDF";
static { static {
// names of all the implementation classes // names of all the implementation classes
// use local variables, only used here // use local variables, only used here
@ -546,6 +549,7 @@ public final class SunPKCS11 extends AuthProvider {
String P11PBECipher = "sun.security.pkcs11.P11PBECipher"; String P11PBECipher = "sun.security.pkcs11.P11PBECipher";
String P11Signature = "sun.security.pkcs11.P11Signature"; String P11Signature = "sun.security.pkcs11.P11Signature";
String P11PSSSignature = "sun.security.pkcs11.P11PSSSignature"; String P11PSSSignature = "sun.security.pkcs11.P11PSSSignature";
String P11HKDF = "sun.security.pkcs11.P11HKDF";
// XXX register all aliases // XXX register all aliases
@ -729,6 +733,8 @@ public final class SunPKCS11 extends AuthProvider {
m(CKM_BLOWFISH_CBC)); m(CKM_BLOWFISH_CBC));
d(SKF, "ChaCha20", P11SecretKeyFactory, d(SKF, "ChaCha20", P11SecretKeyFactory,
m(CKM_CHACHA20_POLY1305)); m(CKM_CHACHA20_POLY1305));
d(SKF, "Generic", P11SecretKeyFactory,
m(CKM_GENERIC_SECRET_KEY_GEN));
/* /*
* PBE Secret Key Factories * PBE Secret Key Factories
@ -1073,6 +1079,13 @@ public final class SunPKCS11 extends AuthProvider {
m(CKM_TLS_PRF, CKM_NSS_TLS_PRF_GENERAL)); m(CKM_TLS_PRF, CKM_NSS_TLS_PRF_GENERAL));
d(KG, "SunTls12Prf", "sun.security.pkcs11.P11TlsPrfGenerator", d(KG, "SunTls12Prf", "sun.security.pkcs11.P11TlsPrfGenerator",
m(CKM_TLS_MAC)); m(CKM_TLS_MAC));
d(KDF, "HKDF-SHA256", P11HKDF, m(CKM_HKDF_DERIVE, CKM_HKDF_DATA),
m(CKM_SHA256_HMAC));
d(KDF, "HKDF-SHA384", P11HKDF, m(CKM_HKDF_DERIVE, CKM_HKDF_DATA),
m(CKM_SHA384_HMAC));
d(KDF, "HKDF-SHA512", P11HKDF, m(CKM_HKDF_DERIVE, CKM_HKDF_DATA),
m(CKM_SHA512_HMAC));
} }
// background thread that periodically checks for token insertion // background thread that periodically checks for token insertion
@ -1507,6 +1520,14 @@ public final class SunPKCS11 extends AuthProvider {
throw new NoSuchAlgorithmException("Unsupported algorithm: " throw new NoSuchAlgorithmException("Unsupported algorithm: "
+ algorithm); + algorithm);
} }
} else if (type == KDF) {
try {
return new P11HKDF(token, algorithm, (KDFParameters) param);
} catch (ClassCastException |
InvalidAlgorithmParameterException e) {
throw new NoSuchAlgorithmException(
"Cannot instantiate a service of KDF type.", e);
}
} else { } else {
throw new NoSuchAlgorithmException("Unknown type: " + type); throw new NoSuchAlgorithmException("Unknown type: " + type);
} }

View file

@ -0,0 +1,174 @@
/*
* Copyright (c) 2025, Red Hat, Inc.
* 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 sun.security.pkcs11.wrapper;
/**
* class CK_HKDF_PARAMS provides the parameters to the CKM_HKDF_DERIVE and
* CKM_HKDF_DATA mechanisms.<p>
* <b>PKCS#11 structure:</b>
* <pre>
* typedef struct CK_HKDF_PARAMS {
* CK_BBOOL bExtract;
* CK_BBOOL bExpand;
* CK_MECHANISM_TYPE prfHashMechanism;
* CK_ULONG ulSaltType;
* CK_BYTE_PTR pSalt;
* CK_ULONG ulSaltLen;
* CK_OBJECT_HANDLE hSaltKey;
* CK_BYTE_PTR pInfo;
* CK_ULONG ulInfoLen;
* } CK_HKDF_PARAMS;
* </pre>
*
*/
public class CK_HKDF_PARAMS {
/**
* <b>PKCS#11:</b>
* <pre>
* CK_BBOOL bExtract;
* </pre>
*/
public final boolean bExtract;
/**
* <b>PKCS#11:</b>
* <pre>
* CK_BBOOL bExpand;
* </pre>
*/
public final boolean bExpand;
/**
* <b>PKCS#11:</b>
* <pre>
* CK_MECHANISM_TYPE prfHashMechanism;
* </pre>
*/
public final long prfHashMechanism;
/**
* <b>PKCS#11:</b>
* <pre>
* CK_ULONG ulSaltType;
* </pre>
*/
public final long ulSaltType;
/**
* <b>PKCS#11:</b>
* <pre>
* CK_BYTE_PTR pSalt;
* CK_ULONG ulSaltLen;
* </pre>
*/
public final byte[] pSalt;
/**
* <b>PKCS#11:</b>
* <pre>
* CK_OBJECT_HANDLE hSaltKey;
* </pre>
*/
public final long hSaltKey;
/**
* <b>PKCS#11:</b>
* <pre>
* CK_BYTE_PTR pInfo;
* CK_ULONG ulInfoLen;
* </pre>
*/
public final byte[] pInfo;
public CK_HKDF_PARAMS(boolean bExtract, boolean bExpand,
long prfHashMechanism, long ulSaltType, byte[] pSalt, long hSaltKey,
byte[] pInfo) {
this.bExtract = bExtract;
this.bExpand = bExpand;
this.prfHashMechanism = prfHashMechanism;
this.ulSaltType = ulSaltType;
this.pSalt = pSalt;
this.hSaltKey = hSaltKey;
this.pInfo = pInfo;
}
/**
* Returns the string representation of CK_HKDF_PARAMS.
*
* @return the string representation of CK_HKDF_PARAMS
*/
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(Constants.INDENT);
sb.append("bExtract: ");
sb.append(bExtract);
sb.append(Constants.NEWLINE);
sb.append(Constants.INDENT);
sb.append("bExpand: ");
sb.append(bExpand);
sb.append(Constants.NEWLINE);
sb.append(Constants.INDENT);
sb.append("prfHashMechanism: ");
sb.append(Functions.getMechanismName(prfHashMechanism));
sb.append(Constants.NEWLINE);
sb.append(Constants.INDENT);
sb.append("ulSaltType: ");
sb.append(Functions.saltTypeToString(ulSaltType));
sb.append(Constants.NEWLINE);
sb.append(Constants.INDENT);
sb.append("pSalt: ");
sb.append(Functions.toHexString(pSalt));
sb.append(Constants.NEWLINE);
sb.append(Constants.INDENT);
sb.append("ulSaltLen: ");
sb.append(Functions.getLength(pSalt));
sb.append(Constants.NEWLINE);
sb.append(Constants.INDENT);
sb.append("hSaltKey: ");
sb.append(hSaltKey);
sb.append(Constants.NEWLINE);
sb.append(Constants.INDENT);
sb.append("pInfo: ");
sb.append(Functions.toHexString(pInfo));
sb.append(Constants.NEWLINE);
sb.append(Constants.INDENT);
sb.append("ulInfoLen: ");
sb.append(Functions.getLength(pInfo));
sb.append(Constants.NEWLINE);
return sb.toString();
}
}

View file

@ -0,0 +1,79 @@
/*
* Copyright (c) 2025, Red Hat, Inc.
* 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 sun.security.pkcs11.wrapper;
/**
* class CK_KEY_DERIVATION_STRING_DATA provides the parameters to the
* CKM_DES_ECB_ENCRYPT_DATA, CKM_DES3_ECB_ENCRYPT_DATA,
* CKM_AES_ECB_ENCRYPT_DATA, CKM_CONCATENATE_BASE_AND_DATA,
* CKM_CONCATENATE_DATA_AND_BASE, CKM_XOR_BASE_AND_DATA,
* CKM_CAMELLIA_ECB_ENCRYPT_DATA, CKM_ARIA_ECB_ENCRYPT_DATA and
* CKM_SEED_ECB_ENCRYPT_DATA mechanisms.<p>
* <b>PKCS#11 structure:</b>
* <pre>
* typedef struct CK_KEY_DERIVATION_STRING_DATA {
* CK_BYTE_PTR pData;
* CK_ULONG ulLen;
* } CK_KEY_DERIVATION_STRING_DATA;
* </pre>
*
*/
public class CK_KEY_DERIVATION_STRING_DATA {
/**
* <b>PKCS#11:</b>
* <pre>
* CK_BYTE_PTR pData;
* CK_ULONG ulLen;
* </pre>
*/
public final byte[] pData;
public CK_KEY_DERIVATION_STRING_DATA(byte[] pData) {
this.pData = pData;
}
/**
* Returns the string representation of CK_KEY_DERIVATION_STRING_DATA.
*
* @return the string representation of CK_KEY_DERIVATION_STRING_DATA
*/
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(Constants.INDENT);
sb.append("pData: ");
sb.append(Functions.toHexString(pData));
sb.append(Constants.NEWLINE);
sb.append(Constants.INDENT);
sb.append("ulLen: ");
sb.append(Functions.getLength(pData));
sb.append(Constants.NEWLINE);
return sb.toString();
}
}

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2003, 2024, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2003, 2025, Oracle and/or its affiliates. All rights reserved.
*/ */
/* Copyright (c) 2002 Graz University of Technology. All rights reserved. /* Copyright (c) 2002 Graz University of Technology. All rights reserved.
@ -127,6 +127,14 @@ public class CK_MECHANISM {
init(mechanism, params); init(mechanism, params);
} }
public CK_MECHANISM(long mechanism, CK_HKDF_PARAMS params) {
init(mechanism, params);
}
public CK_MECHANISM(long mechanism, CK_KEY_DERIVATION_STRING_DATA params) {
init(mechanism, params);
}
public CK_MECHANISM(long mechanism, CK_ECDH1_DERIVE_PARAMS params) { public CK_MECHANISM(long mechanism, CK_ECDH1_DERIVE_PARAMS params) {
init(mechanism, params); init(mechanism, params);
} }

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2003, 2023, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2003, 2025, Oracle and/or its affiliates. All rights reserved.
*/ */
/* Copyright (c) 2002 Graz University of Technology. All rights reserved. /* Copyright (c) 2002 Graz University of Technology. All rights reserved.
@ -440,6 +440,26 @@ public class Functions {
return mechanismInfoFlags.toString(flags); return mechanismInfoFlags.toString(flags);
} }
private static final Flags hkdfSaltTypes = new Flags(new long[] {
CKF_HKDF_SALT_NULL,
CKF_HKDF_SALT_DATA,
CKF_HKDF_SALT_KEY
}, new String[] {
"CKF_HKDF_SALT_NULL",
"CKF_HKDF_SALT_DATA",
"CKF_HKDF_SALT_KEY"
});
/**
* converts a long value to a HKDF salt type string
*
* @param ulSaltType the HKDF salt type to be converted
* @return the HKDF salt type string representation
*/
public static String saltTypeToString(long ulSaltType) {
return hkdfSaltTypes.toString(ulSaltType);
}
private static String getName(Map<Integer,String> nameMap, long id) { private static String getName(Map<Integer,String> nameMap, long id) {
String name = null; String name = null;
if ((id >>> 32) == 0) { if ((id >>> 32) == 0) {

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2003, 2023, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2003, 2025, Oracle and/or its affiliates. All rights reserved.
*/ */
/* Copyright (c) 2002 Graz University of Technology. All rights reserved. /* Copyright (c) 2002 Graz University of Technology. All rights reserved.
@ -311,8 +311,6 @@ public interface PKCS11Constants {
// pseudo key type ANY (for template manager) // pseudo key type ANY (for template manager)
public static final long PCKK_ANY = 0x7FFFFF22L; public static final long PCKK_ANY = 0x7FFFFF22L;
public static final long PCKK_HMAC = 0x7FFFFF23L;
public static final long PCKK_SSLMAC = 0x7FFFFF24L;
public static final long PCKK_TLSPREMASTER = 0x7FFFFF25L; public static final long PCKK_TLSPREMASTER = 0x7FFFFF25L;
public static final long PCKK_TLSRSAPREMASTER = 0x7FFFFF26L; public static final long PCKK_TLSRSAPREMASTER = 0x7FFFFF26L;
public static final long PCKK_TLSMASTER = 0x7FFFFF27L; public static final long PCKK_TLSMASTER = 0x7FFFFF27L;
@ -1156,11 +1154,11 @@ public interface PKCS11Constants {
= 0x00000001L; = 0x00000001L;
public static final long CK_SP800_108_DKM_LENGTH_SUM_OF_SEGMENTS public static final long CK_SP800_108_DKM_LENGTH_SUM_OF_SEGMENTS
= 0x00000002L; = 0x00000002L;
*/
public static final long CKF_HKDF_SALT_NULL = 0x00000001L; public static final long CKF_HKDF_SALT_NULL = 0x00000001L;
public static final long CKF_HKDF_SALT_DATA = 0x00000002L; public static final long CKF_HKDF_SALT_DATA = 0x00000002L;
public static final long CKF_HKDF_SALT_KEY = 0x00000004L; public static final long CKF_HKDF_SALT_KEY = 0x00000004L;
*/
// private NSS attribute (for DSA and DH private keys) // private NSS attribute (for DSA and DH private keys)
public static final long CKA_NETSCAPE_DB = 0xD5A0DB00L; public static final long CKA_NETSCAPE_DB = 0xD5A0DB00L;

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2003, 2024, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2003, 2025, Oracle and/or its affiliates. All rights reserved.
*/ */
/* Copyright (c) 2002 Graz University of Technology. All rights reserved. /* Copyright (c) 2002 Graz University of Technology. All rights reserved.
@ -731,6 +731,150 @@ jTlsMacParamsToCKTlsMacParamPtr(JNIEnv *env, jobject jParam, CK_ULONG *pLength)
return ckParamPtr; return ckParamPtr;
} }
/*
* converts a Java CK_HKDF_PARAMS object to a CK_HKDF_PARAMS pointer
*
* @param env - used to call JNI functions to get Java classes and objects
* @param jParam - a Java CK_HKDF_PARAMS object to convert
* @param pLength - length of the allocated memory of the returned pointer
* @return pointer to a new CK_HKDF_PARAMS structure
*/
CK_HKDF_PARAMS_PTR
jHkdfParamsToCKHkdfParamsPtr(JNIEnv *env, jobject jParam, CK_ULONG* pLength)
{
CK_HKDF_PARAMS_PTR ckParamPtr = NULL;
jclass jHkdfParamsClass;
jfieldID fieldID;
jboolean jbExtract, jbExpand;
jlong jPrfHashMechanism, julSaltType, jhSaltKey;
jobject jpSalt, jpInfo;
if (pLength != NULL) {
*pLength = 0L;
}
jHkdfParamsClass = (*env)->FindClass(env, CLASS_HKDF_PARAMS);
if (jHkdfParamsClass == NULL) {
return NULL;
}
fieldID = (*env)->GetFieldID(env, jHkdfParamsClass, "bExtract", "Z");
if (fieldID == NULL) {
return NULL;
}
jbExtract = (*env)->GetBooleanField(env, jParam, fieldID);
fieldID = (*env)->GetFieldID(env, jHkdfParamsClass, "bExpand", "Z");
if (fieldID == NULL) {
return NULL;
}
jbExpand = (*env)->GetBooleanField(env, jParam, fieldID);
fieldID = (*env)->GetFieldID(env, jHkdfParamsClass, "prfHashMechanism", "J");
if (fieldID == NULL) {
return NULL;
}
jPrfHashMechanism = (*env)->GetLongField(env, jParam, fieldID);
fieldID = (*env)->GetFieldID(env, jHkdfParamsClass, "ulSaltType", "J");
if (fieldID == NULL) {
return NULL;
}
julSaltType = (*env)->GetLongField(env, jParam, fieldID);
fieldID = (*env)->GetFieldID(env, jHkdfParamsClass, "pSalt", "[B");
if (fieldID == NULL) {
return NULL;
}
jpSalt = (*env)->GetObjectField(env, jParam, fieldID);
fieldID = (*env)->GetFieldID(env, jHkdfParamsClass, "hSaltKey", "J");
if (fieldID == NULL) {
return NULL;
}
jhSaltKey = (*env)->GetLongField(env, jParam, fieldID);
fieldID = (*env)->GetFieldID(env, jHkdfParamsClass, "pInfo", "[B");
if (fieldID == NULL) {
return NULL;
}
jpInfo = (*env)->GetObjectField(env, jParam, fieldID);
ckParamPtr = calloc(1, sizeof(CK_HKDF_PARAMS));
if (ckParamPtr == NULL) {
p11ThrowOutOfMemoryError(env, 0);
return NULL;
}
ckParamPtr->bExtract = jBooleanToCKBBool(jbExtract);
ckParamPtr->bExpand = jBooleanToCKBBool(jbExpand);
ckParamPtr->prfHashMechanism = jLongToCKULong(jPrfHashMechanism);
ckParamPtr->ulSaltType = jLongToCKULong(julSaltType);
jByteArrayToCKByteArray(env, jpSalt, &(ckParamPtr->pSalt), &(ckParamPtr->ulSaltLen));
if ((*env)->ExceptionCheck(env)) {
goto cleanup;
}
ckParamPtr->hSaltKey = jLongToCKULong(jhSaltKey);
jByteArrayToCKByteArray(env, jpInfo, &(ckParamPtr->pInfo), &(ckParamPtr->ulInfoLen));
if ((*env)->ExceptionCheck(env)) {
goto cleanup;
}
if (pLength != NULL) {
*pLength = sizeof(CK_HKDF_PARAMS);
}
return ckParamPtr;
cleanup:
free(ckParamPtr->pInfo);
free(ckParamPtr->pSalt);
free(ckParamPtr);
return NULL;
}
/*
* converts a Java CK_KEY_DERIVATION_STRING_DATA object to a CK_KEY_DERIVATION_STRING_DATA pointer
*
* @param env - used to call JNI functions to get Java classes and objects
* @param jParam - a Java CK_KEY_DERIVATION_STRING_DATA object to convert
* @param pLength - length of the allocated memory of the returned pointer
* @return pointer to a new CK_KEY_DERIVATION_STRING_DATA structure
*/
CK_KEY_DERIVATION_STRING_DATA_PTR
jKeyDerivationStringDataToCKKeyDerivationStringDataPtr(JNIEnv *env, jobject jParam, CK_ULONG *pLength)
{
CK_KEY_DERIVATION_STRING_DATA_PTR ckParamPtr = NULL;
jclass jKeyDerivationStringDataClass;
jfieldID fieldID;
jobject jpData;
if (pLength != NULL) {
*pLength = 0L;
}
jKeyDerivationStringDataClass = (*env)->FindClass(env, CLASS_KEY_DERIVATION_STRING_DATA);
if (jKeyDerivationStringDataClass == NULL) {
return NULL;
}
fieldID = (*env)->GetFieldID(env, jKeyDerivationStringDataClass, "pData", "[B");
if (fieldID == NULL) {
return NULL;
}
jpData = (*env)->GetObjectField(env, jParam, fieldID);
ckParamPtr = calloc(1, sizeof(CK_KEY_DERIVATION_STRING_DATA));
if (ckParamPtr == NULL) {
p11ThrowOutOfMemoryError(env, 0);
return NULL;
}
jByteArrayToCKByteArray(env, jpData, &(ckParamPtr->pData), &(ckParamPtr->ulLen));
if ((*env)->ExceptionCheck(env)) {
goto cleanup;
}
if (pLength != NULL) {
*pLength = sizeof(CK_KEY_DERIVATION_STRING_DATA);
}
return ckParamPtr;
cleanup:
free(ckParamPtr->pData);
free(ckParamPtr);
return NULL;
}
void keyMatParamToCKKeyMatParam(JNIEnv *env, jobject jParam, void keyMatParamToCKKeyMatParam(JNIEnv *env, jobject jParam,
jclass jKeyMatParamClass, jclass jKeyMatParamClass,
CK_ULONG* cKKeyMatParamUlMacSizeInBits, CK_ULONG* cKKeyMatParamUlMacSizeInBits,
@ -1497,6 +1641,15 @@ CK_VOID_PTR jMechParamToCKMechParamPtrSlow(JNIEnv *env, jobject jParam,
ckpParamPtr = jTlsMacParamsToCKTlsMacParamPtr(env, jParam, ckpParamPtr = jTlsMacParamsToCKTlsMacParamPtr(env, jParam,
ckpLength); ckpLength);
break; break;
case CKM_HKDF_DATA:
case CKM_HKDF_DERIVE:
ckpParamPtr = jHkdfParamsToCKHkdfParamsPtr(env, jParam, ckpLength);
break;
case CKM_CONCATENATE_BASE_AND_DATA:
case CKM_CONCATENATE_DATA_AND_BASE:
ckpParamPtr = jKeyDerivationStringDataToCKKeyDerivationStringDataPtr(env, jParam,
ckpLength);
break;
case CKM_AES_CTR: case CKM_AES_CTR:
ckpParamPtr = jAesCtrParamsToCKAesCtrParamPtr(env, jParam, ckpParamPtr = jAesCtrParamsToCKAesCtrParamPtr(env, jParam,
ckpLength); ckpLength);

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2003, 2024, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2003, 2025, Oracle and/or its affiliates. All rights reserved.
*/ */
/* Copyright (c) 2002 Graz University of Technology. All rights reserved. /* Copyright (c) 2002 Graz University of Technology. All rights reserved.
@ -355,6 +355,16 @@ void freeCKMechanismPtr(CK_MECHANISM_PTR mechPtr) {
free(((CK_TLS_PRF_PARAMS*)tmp)->pulOutputLen); free(((CK_TLS_PRF_PARAMS*)tmp)->pulOutputLen);
free(((CK_TLS_PRF_PARAMS*)tmp)->pOutput); free(((CK_TLS_PRF_PARAMS*)tmp)->pOutput);
break; break;
case CKM_HKDF_DERIVE:
TRACE0("[ CK_HKDF_PARAMS ]\n");
free(((CK_HKDF_PARAMS*)tmp)->pSalt);
free(((CK_HKDF_PARAMS*)tmp)->pInfo);
break;
case CKM_CONCATENATE_BASE_AND_DATA:
case CKM_CONCATENATE_DATA_AND_BASE:
TRACE0("[ CK_KEY_DERIVATION_STRING_DATA ]\n");
free(((CK_KEY_DERIVATION_STRING_DATA*)tmp)->pData);
break;
case CKM_SSL3_MASTER_KEY_DERIVE: case CKM_SSL3_MASTER_KEY_DERIVE:
case CKM_TLS_MASTER_KEY_DERIVE: case CKM_TLS_MASTER_KEY_DERIVE:
case CKM_SSL3_MASTER_KEY_DERIVE_DH: case CKM_SSL3_MASTER_KEY_DERIVE_DH:

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2003, 2024, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2003, 2025, Oracle and/or its affiliates. All rights reserved.
*/ */
/* Copyright (c) 2002 Graz University of Technology. All rights reserved. /* Copyright (c) 2002 Graz University of Technology. All rights reserved.
@ -297,9 +297,9 @@ void printDebug(const char *format, ...);
#define CLASS_SKIPJACK_PRIVATE_WRAP_PARAMS "sun/security/pkcs11/wrapper/CK_SKIPJACK_PRIVATE_WRAP_PARAMS" #define CLASS_SKIPJACK_PRIVATE_WRAP_PARAMS "sun/security/pkcs11/wrapper/CK_SKIPJACK_PRIVATE_WRAP_PARAMS"
#define CLASS_SKIPJACK_RELAYX_PARAMS "sun/security/pkcs11/wrapper/CK_SKIPJACK_RELAYX_PARAMS" #define CLASS_SKIPJACK_RELAYX_PARAMS "sun/security/pkcs11/wrapper/CK_SKIPJACK_RELAYX_PARAMS"
#define CLASS_KEY_WRAP_SET_OAEP_PARAMS "sun/security/pkcs11/wrapper/CK_KEY_WRAP_SET_OAEP_PARAMS" #define CLASS_KEY_WRAP_SET_OAEP_PARAMS "sun/security/pkcs11/wrapper/CK_KEY_WRAP_SET_OAEP_PARAMS"
#define CLASS_KEY_DERIVATION_STRING_DATA "sun/security/pkcs11/wrapper/CK_KEY_DERIVATION_STRING_DATA"
*/ */
#define CLASS_KEY_DERIVATION_STRING_DATA "sun/security/pkcs11/wrapper/CK_KEY_DERIVATION_STRING_DATA"
#define CLASS_SSL3_RANDOM_DATA "sun/security/pkcs11/wrapper/CK_SSL3_RANDOM_DATA" #define CLASS_SSL3_RANDOM_DATA "sun/security/pkcs11/wrapper/CK_SSL3_RANDOM_DATA"
// CLASS_SSL3_RANDOM_DATA is used by CLASS_SSL3_MASTER_KEY_DERIVE_PARAMS // CLASS_SSL3_RANDOM_DATA is used by CLASS_SSL3_MASTER_KEY_DERIVE_PARAMS
#define CLASS_SSL3_KEY_MAT_OUT "sun/security/pkcs11/wrapper/CK_SSL3_KEY_MAT_OUT" #define CLASS_SSL3_KEY_MAT_OUT "sun/security/pkcs11/wrapper/CK_SSL3_KEY_MAT_OUT"
@ -310,6 +310,7 @@ void printDebug(const char *format, ...);
#define CLASS_TLS12_KEY_MAT_PARAMS "sun/security/pkcs11/wrapper/CK_TLS12_KEY_MAT_PARAMS" #define CLASS_TLS12_KEY_MAT_PARAMS "sun/security/pkcs11/wrapper/CK_TLS12_KEY_MAT_PARAMS"
#define CLASS_TLS_PRF_PARAMS "sun/security/pkcs11/wrapper/CK_TLS_PRF_PARAMS" #define CLASS_TLS_PRF_PARAMS "sun/security/pkcs11/wrapper/CK_TLS_PRF_PARAMS"
#define CLASS_TLS_MAC_PARAMS "sun/security/pkcs11/wrapper/CK_TLS_MAC_PARAMS" #define CLASS_TLS_MAC_PARAMS "sun/security/pkcs11/wrapper/CK_TLS_MAC_PARAMS"
#define CLASS_HKDF_PARAMS "sun/security/pkcs11/wrapper/CK_HKDF_PARAMS"
/* function to update the CK_NSS_GCM_PARAMS in mechanism pointer with /* function to update the CK_NSS_GCM_PARAMS in mechanism pointer with
* CK_GCM_PARAMS * CK_GCM_PARAMS
@ -394,7 +395,8 @@ CK_PBE_PARAMS_PTR jPbeParamToCKPbeParamPtr(JNIEnv *env, jobject jParam, CK_ULONG
CK_VOID_PTR jPkcs5Pbkd2ParamToCKPkcs5Pbkd2ParamPtr(JNIEnv *env, jobject jParam, CK_ULONG* pLength); CK_VOID_PTR jPkcs5Pbkd2ParamToCKPkcs5Pbkd2ParamPtr(JNIEnv *env, jobject jParam, CK_ULONG* pLength);
CK_SSL3_MASTER_KEY_DERIVE_PARAMS_PTR jSsl3MasterKeyDeriveParamToCKSsl3MasterKeyDeriveParamPtr(JNIEnv *env, jobject jParam, CK_ULONG* pLength); CK_SSL3_MASTER_KEY_DERIVE_PARAMS_PTR jSsl3MasterKeyDeriveParamToCKSsl3MasterKeyDeriveParamPtr(JNIEnv *env, jobject jParam, CK_ULONG* pLength);
CK_SSL3_KEY_MAT_PARAMS_PTR jSsl3KeyMatParamToCKSsl3KeyMatParamPtr(JNIEnv *env, jobject jParam, CK_ULONG* pLength); CK_SSL3_KEY_MAT_PARAMS_PTR jSsl3KeyMatParamToCKSsl3KeyMatParamPtr(JNIEnv *env, jobject jParam, CK_ULONG* pLength);
CK_KEY_DERIVATION_STRING_DATA jKeyDerivationStringDataToCKKeyDerivationStringData(JNIEnv *env, jobject jParam); CK_HKDF_PARAMS_PTR jHkdfParamsToCKHkdfParamsPtr(JNIEnv *env, jobject jParam, CK_ULONG* pLength);
CK_KEY_DERIVATION_STRING_DATA_PTR jKeyDerivationStringDataToCKKeyDerivationStringDataPtr(JNIEnv *env, jobject jParam, CK_ULONG* pLength);
CK_RSA_PKCS_PSS_PARAMS_PTR jRsaPkcsPssParamToCKRsaPkcsPssParamPtr(JNIEnv *env, jobject jParam, CK_ULONG* pLength); CK_RSA_PKCS_PSS_PARAMS_PTR jRsaPkcsPssParamToCKRsaPkcsPssParamPtr(JNIEnv *env, jobject jParam, CK_ULONG* pLength);
CK_ECDH1_DERIVE_PARAMS_PTR jEcdh1DeriveParamToCKEcdh1DeriveParamPtr(JNIEnv *env, jobject jParam, CK_ULONG* pLength); CK_ECDH1_DERIVE_PARAMS_PTR jEcdh1DeriveParamToCKEcdh1DeriveParamPtr(JNIEnv *env, jobject jParam, CK_ULONG* pLength);
CK_ECDH2_DERIVE_PARAMS_PTR jEcdh2DeriveParamToCKEcdh2DeriveParamPtr(JNIEnv *env, jobject jParam, CK_ULONG* pLength); CK_ECDH2_DERIVE_PARAMS_PTR jEcdh2DeriveParamToCKEcdh2DeriveParamPtr(JNIEnv *env, jobject jParam, CK_ULONG* pLength);

View file

@ -0,0 +1,634 @@
/*
* Copyright (c) 2025, Red Hat, Inc.
*
* 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.
*/
import javax.crypto.*;
import javax.crypto.spec.*;
import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.lang.reflect.Method;
import java.math.BigInteger;
import java.security.*;
import java.security.spec.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.HexFormat;
import java.util.List;
/*
* @test
* @bug 8328119
* @summary test HKDF key derivation in SunPKCS11
* @library /test/lib ..
* @enablePreview
* @run main/othervm/timeout=30 TestHKDF
*/
public final class TestHKDF extends PKCS11Test {
private static final boolean DEBUG = false;
private static final HexFormat hex = HexFormat.of().withLowerCase();
private static final byte[] knownBytes = hex.parseHex(
"000102030405060708090a0b0c0d0e0f10111213141516");
private static final OutputStream debugOut = new ByteArrayOutputStream();
private static final PrintWriter debugPrinter = new PrintWriter(debugOut);
private static boolean testFailed = false;
private static Provider p11Provider;
private static SecretKeyFactory p11GenericSkf;
private record TestContext(String hkdfAlg, String derivedKeyAlg,
Supplier<SecretKey> baseKey, byte[] salt, byte[] info,
byte[] expectedPRK, byte[] expectedOKM, byte[] expectedOpOut) {
// expectedOpOut value:
// - If derivedKeyAlg is AES, expectedOpOut is the result of encrypting
// knownBytes with derivedKey, using AES/CBC/PKCS5Padding and an IV
// of 16 zero bytes.
// - If derivedKeyAlg is Generic, expectedOpOut is the result of
// calculating the HmacSHA256 of knownBytes with derivedKey.
}
private static class HkdfTestAssertionException extends Exception {
HkdfTestAssertionException(String msg) {
super(msg);
}
}
private enum KdfParamSpecType {
EXTRACT,
EXPAND,
EXTRACT_EXPAND
}
private enum KeyMaterialType {
KEY,
DATA
}
private static final List<List<KeyMaterialType>> keyMaterialCombinations =
List.of(
List.of(KeyMaterialType.KEY),
List.of(KeyMaterialType.DATA),
List.of(KeyMaterialType.KEY, KeyMaterialType.KEY),
List.of(KeyMaterialType.KEY, KeyMaterialType.DATA),
List.of(KeyMaterialType.DATA, KeyMaterialType.KEY),
List.of(KeyMaterialType.DATA, KeyMaterialType.DATA)
);
private static void addKeyMaterial(
List<KeyMaterialType> keyMaterialCombination, byte[] keyMaterial,
Consumer<SecretKey> addKeyCb, Consumer<byte[]> addDataCb)
throws Exception {
if (keyMaterial.length < keyMaterialCombination.size()) {
throw new Exception("Key material is not enough to fulfill the " +
"combination requirement.");
}
int dataStart = 0, dataEnd;
for (int i = 0; i < keyMaterialCombination.size(); i++) {
dataEnd =
keyMaterial.length - keyMaterialCombination.size() + i + 1;
byte[] chunk = Arrays.copyOfRange(keyMaterial, dataStart, dataEnd);
if (keyMaterialCombination.get(i) == KeyMaterialType.KEY) {
addKeyCb.accept(p11GenericSkf.generateSecret(
new SecretKeySpec(chunk, "Generic")));
} else {
addDataCb.accept(chunk);
}
dataStart = dataEnd;
}
}
private static List<AlgorithmParameterSpec> generateKdfParamSpecs(
TestContext ctx, KdfParamSpecType type) throws Exception {
List<AlgorithmParameterSpec> kdfParamSpecs = new ArrayList<>();
if (type == KdfParamSpecType.EXTRACT ||
type == KdfParamSpecType.EXTRACT_EXPAND) {
for (List<KeyMaterialType> keyMaterialCombination :
keyMaterialCombinations) {
final HKDFParameterSpec.Builder b =
HKDFParameterSpec.ofExtract();
SecretKey baseKey = ctx.baseKey.get();
if (baseKey instanceof SecretKeySpec) {
addKeyMaterial(keyMaterialCombination, baseKey.getEncoded(),
b::addIKM, b::addIKM);
} else if (baseKey != null) {
b.addIKM(baseKey);
}
if (ctx.salt != null) {
addKeyMaterial(keyMaterialCombination, ctx.salt, b::addSalt,
b::addSalt);
}
if (type == KdfParamSpecType.EXTRACT) {
kdfParamSpecs.add(b.extractOnly());
} else {
kdfParamSpecs.add(b.thenExpand(ctx.info,
ctx.expectedOKM.length));
}
if (ctx.salt == null && !(baseKey instanceof SecretKeySpec)) {
// If the salt is null and the IKM is a non-SecretKeySpec
// (i.e. is a P11Key.P11SecretKey), the key material
// cannot be split and there will be a single
// HKDFParameterSpec to test.
break;
}
}
} else {
assert type == KdfParamSpecType.EXPAND : "Unexpected type.";
kdfParamSpecs.add(HKDFParameterSpec.expandOnly(
new SecretKeySpec(ctx.expectedPRK, "Generic"), ctx.info,
ctx.expectedOKM.length));
}
return kdfParamSpecs;
}
private static void checkOpWithDerivedKey(TestContext ctx,
SecretKey derivedKey, Provider p) throws Exception {
byte[] opOut;
switch (ctx.derivedKeyAlg) {
case "AES" -> {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding", p);
cipher.init(Cipher.ENCRYPT_MODE, derivedKey,
new IvParameterSpec(new byte[16]));
opOut = cipher.doFinal(knownBytes);
}
case "Generic" -> {
Mac hmac = Mac.getInstance("HmacSHA256", p);
hmac.init(derivedKey);
opOut = hmac.doFinal(knownBytes);
}
default -> throw new RuntimeException(
"Unexpected derived key algorithm.");
}
printByteArrayAssertion("Operation output", opOut, ctx.expectedOpOut);
if (!Arrays.equals(opOut, ctx.expectedOpOut)) {
throw new HkdfTestAssertionException(
"Operation with derived key failure.");
}
}
private static void checkDerivationData(String derivationType,
byte[] derivedKey, byte[] derivedData, byte[] expectedData)
throws Exception {
printByteArrayAssertion(derivationType + " key derivation", derivedKey,
expectedData);
printByteArrayAssertion(derivationType + " data derivation",
derivedData, expectedData);
if (!Arrays.equals(derivedKey, expectedData) ||
!Arrays.equals(derivedData, expectedData)) {
throw new HkdfTestAssertionException(
derivationType + " derivation failure.");
}
}
private static void executeDerivationForKdfParamSpec(TestContext ctx,
KdfParamSpecType type, KDF kdf, AlgorithmParameterSpec kdfParamSpec,
Provider p) throws Exception {
printDerivationInfo(ctx, type, kdfParamSpec, p);
printHeader("HKDF derivation: output", '-', 10);
String derivedKeyAlg = type == KdfParamSpecType.EXTRACT ?
"Generic" : ctx.derivedKeyAlg;
SecretKey derivedKey = kdf.deriveKey(derivedKeyAlg, kdfParamSpec);
byte[] derivedData = kdf.deriveData(kdfParamSpec);
if (type == KdfParamSpecType.EXPAND ||
type == KdfParamSpecType.EXTRACT_EXPAND) {
checkDerivationData("Extract", derivedKey.getEncoded(),
derivedData, ctx.expectedOKM);
checkOpWithDerivedKey(ctx, derivedKey, p);
} else {
assert type == KdfParamSpecType.EXTRACT : "Unexpected type.";
checkDerivationData("Expand", derivedKey.getEncoded(),
derivedData, ctx.expectedPRK);
}
}
private static void crossCheckTestExpectedData(TestContext ctx,
KdfParamSpecType type, AlgorithmParameterSpec kdfParamSpec)
throws Exception {
try {
Provider sunJCE = Security.getProvider("SunJCE");
KDF kdf = KDF.getInstance(ctx.hkdfAlg, sunJCE);
executeDerivationForKdfParamSpec(ctx, type, kdf, kdfParamSpec,
sunJCE);
} catch (HkdfTestAssertionException e) {
// Fail if derivation was possible and the assertion data in this
// test is inconsistent with SunJCE. This should never happen.
throw e;
} catch (Exception e) {
// Cross-checking of the expected data in this test is a
// best-effort. If derivation was not possible (e.g. SunJCE does
// not support the HKDF algorithm), do not fail.
}
}
private static void reportTestFailure(Exception e) {
testFailed = true;
printHeader("TEST FAILED", 'x', 10);
e.printStackTrace(debugPrinter);
}
private static void executeDerivation(TestContext ctx,
KdfParamSpecType type) {
try {
KDF kdf = KDF.getInstance(ctx.hkdfAlg, p11Provider);
List<AlgorithmParameterSpec> kdfParamSpecs =
generateKdfParamSpecs(ctx, type);
crossCheckTestExpectedData(ctx, type, kdfParamSpecs.get(0));
for (AlgorithmParameterSpec kdfParamSpec : kdfParamSpecs) {
executeDerivationForKdfParamSpec(ctx, type, kdf, kdfParamSpec,
p11Provider);
}
} catch (Exception e) {
reportTestFailure(e);
}
}
private static byte[] hexStringToByteArray(String hexString) {
return hexString != null ? hex.parseHex(hexString) : null;
}
private static void executeTest(String testHeader, String hkdfAlg,
String derivedKeyAlg, SecretKey baseKey, String saltHex,
String infoHex, String expectedPRKHex, String expectedOKMHex,
String expectedOpOutHex) {
executeTest(testHeader, hkdfAlg, derivedKeyAlg, ()-> baseKey, saltHex,
infoHex, expectedPRKHex, expectedOKMHex, expectedOpOutHex);
}
private static void executeTest(String testHeader, String hkdfAlg,
String derivedKeyAlg, String baseKeyHex, String saltHex,
String infoHex, String expectedPRKHex, String expectedOKMHex,
String expectedOpOutHex) {
executeTest(testHeader, hkdfAlg, derivedKeyAlg,
()-> new SecretKeySpec(hexStringToByteArray(baseKeyHex),
"Generic"), saltHex, infoHex, expectedPRKHex,
expectedOKMHex, expectedOpOutHex);
}
private static void executeTest(String testHeader, String hkdfAlg,
String derivedKeyAlg, Supplier<SecretKey> baseKey, String saltHex,
String infoHex, String expectedPRKHex, String expectedOKMHex,
String expectedOpOutHex) {
printTestHeader(testHeader);
TestContext ctx = new TestContext(hkdfAlg, derivedKeyAlg, baseKey,
hexStringToByteArray(saltHex),
hexStringToByteArray(infoHex),
hexStringToByteArray(expectedPRKHex),
hexStringToByteArray(expectedOKMHex),
hexStringToByteArray(expectedOpOutHex));
executeDerivation(ctx, KdfParamSpecType.EXTRACT_EXPAND);
executeDerivation(ctx, KdfParamSpecType.EXTRACT);
executeDerivation(ctx, KdfParamSpecType.EXPAND);
}
private static void printTestHeader(String testHeader) {
debugPrinter.println();
debugPrinter.println("=".repeat(testHeader.length()));
debugPrinter.println(testHeader);
debugPrinter.println("=".repeat(testHeader.length()));
}
private static void printHeader(String header, char sepChar, int sepCount) {
String sepBlock = String.valueOf(sepChar).repeat(sepCount);
debugPrinter.println(sepBlock + " " + header + " " + sepBlock);
}
private static void printDerivationKeyMaterial(String header,
List<SecretKey> keyMaterial, KdfParamSpecType type) {
if (keyMaterial != null && !keyMaterial.isEmpty()) {
debugPrinter.println(header + ":");
for (SecretKey km : keyMaterial) {
debugPrinter.print(" ".repeat(2));
if (km instanceof SecretKeySpec) {
debugPrinter.println(hex.formatHex(km.getEncoded()));
} else {
debugPrinter.println(km);
}
}
} else if (type == KdfParamSpecType.EXTRACT ||
type == KdfParamSpecType.EXTRACT_EXPAND) {
debugPrinter.println(header + ": NULL");
}
}
private static void printDerivationInfo(TestContext ctx,
KdfParamSpecType type, AlgorithmParameterSpec kdfParamSpec,
Provider p) {
debugPrinter.println();
printHeader("HKDF derivation: input", '-', 10);
debugPrinter.println("Algorithm: " + ctx.hkdfAlg);
debugPrinter.println("Provider: " + p.getName());
debugPrinter.println("Derivation type: " + type);
List<SecretKey> ikms = null;
List<SecretKey> salts = null;
byte[] info = null;
Integer length = null;
switch (kdfParamSpec) {
case HKDFParameterSpec.Extract asExtract -> {
debugPrinter.println("Derived key type: PRK (Generic)");
salts = asExtract.salts();
ikms = asExtract.ikms();
}
case HKDFParameterSpec.ExtractThenExpand asExtractExpand -> {
debugPrinter.println("Derived key type: " + ctx.derivedKeyAlg);
salts = asExtractExpand.salts();
ikms = asExtractExpand.ikms();
info = asExtractExpand.info();
length = asExtractExpand.length();
}
case HKDFParameterSpec.Expand asExpand -> {
debugPrinter.println("Derived key type: " + ctx.derivedKeyAlg);
info = asExpand.info();
length = asExpand.length();
}
case null, default -> throw new RuntimeException(
"Unrecognized AlgorithmParameterSpec class.");
}
printDerivationKeyMaterial("Salts", salts, type);
printDerivationKeyMaterial("IKMs", ikms, type);
if (info != null) {
debugPrinter.println("Info: " + hex.formatHex(info));
} else if (type == KdfParamSpecType.EXPAND ||
type == KdfParamSpecType.EXTRACT_EXPAND) {
debugPrinter.println("Info: NULL");
}
if (length != null) {
debugPrinter.println("Length: " + length);
}
}
private static void printByteArrayAssertion(String desc, byte[] actual,
byte[] expected) {
debugPrinter.println(desc + " (actual):");
debugPrinter.println(actual != null ? hex.formatHex(actual) : "null");
debugPrinter.println(desc + " (expected):");
debugPrinter.println(expected != null ? hex.formatHex(expected) :
"null");
}
private static SecretKey doKeyAgreement(String algorithm, PrivateKey privK,
PublicKey pubK) throws Exception {
KeyAgreement ka = KeyAgreement.getInstance(algorithm, p11Provider);
ka.init(privK);
ka.doPhase(pubK, true);
return ka.generateSecret("TlsPremasterSecret");
}
private static SecretKey getTlsPremasterSecretWithDHExchange(String xHex,
String yHex, String pHex, String gHex) throws Exception {
KeyFactory kf = KeyFactory.getInstance("DH", p11Provider);
BigInteger p = new BigInteger(pHex, 16);
BigInteger g = new BigInteger(gHex, 16);
PrivateKey privK = kf.generatePrivate(new DHPrivateKeySpec(
new BigInteger(xHex, 16), p, g));
PublicKey pubK = kf.generatePublic(new DHPublicKeySpec(
new BigInteger(yHex, 16), p, g));
return doKeyAgreement("DH", privK, pubK);
}
private static SecretKey getTlsPremasterSecretWithECDHExchange(String s,
String wx, String wy) throws Exception {
AlgorithmParameters p =
AlgorithmParameters.getInstance("EC", p11Provider);
p.init(new ECGenParameterSpec("secp256r1"));
ECParameterSpec params = p.getParameterSpec(ECParameterSpec.class);
KeyFactory kf = KeyFactory.getInstance("EC", p11Provider);
PrivateKey privK = kf.generatePrivate(new ECPrivateKeySpec(
new BigInteger(s), params));
ECPoint publicPoint = new ECPoint(new BigInteger(wx),
new BigInteger(wy));
PublicKey pubK = kf.generatePublic(new ECPublicKeySpec(
publicPoint, params));
return doKeyAgreement("ECDH", privK, pubK);
}
private static void test_RFC_5869_case_1() {
executeTest("RFC 5869 - Test Case 1",
"HKDF-SHA256",
"Generic",
"0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b",
"000102030405060708090a0b0c",
"f0f1f2f3f4f5f6f7f8f9",
"077709362c2e32df0ddc3f0dc47bba6390b6c73bb50f9c3122ec844ad7c2" +
"b3e5",
"3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4" +
"c5bf34007208d5b887185865",
"ad9e90d0c59d47539899647a3baf0fd364c54eeb5f4d0b80e1f39579e434" +
"e801");
}
private static void test_RFC_5869_case_2() {
executeTest("RFC 5869 - Test Case 2",
"HKDF-SHA256",
"Generic",
"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d" +
"1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b" +
"3c3d3e3f404142434445464748494a4b4c4d4e4f",
"606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d" +
"7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b" +
"9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeaf",
"b0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccd" +
"cecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaeb" +
"ecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
"06a6b88c5853361a06104c9ceb35b45cef760014904671014a193f40c15f" +
"c244",
"b11e398dc80327a1c8e7f78c596a49344f012eda2d4efad8a050cc4c19af" +
"a97c59045a99cac7827271cb41c65e590e09da3275600c2f09b8367793a9" +
"aca3db71cc30c58179ec3e87c14c01d5c1f3434f1d87",
"eabe8bc548bf430aedc423e9d7df94125eacff3dbb3b95b50379246c2546" +
"01da");
}
private static void test_RFC_5869_case_3() {
executeTest("RFC 5869 - Test Case 3",
"HKDF-SHA256",
"Generic",
"0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b",
null,
null,
"19ef24a32c717b167f33a91d6f648bdf96596776afdb6377ac434c1c293c" +
"cb04",
"8da4e775a563c18f715f802a063c5a31b8a11f5c5ee1879ec3454e5f3c73" +
"8d2d9d201395faa4b61a96c8",
"06828b5679679681be59aa2822869cb1a174319e53a545e3301bd832ae3e" +
"513f");
}
private static void test_AES_HKDFWithHmacSHA256() {
executeTest("AES - HKDF-SHA256",
"HKDF-SHA256",
"AES",
"000102030405060708090a0b0c0d0e0f",
"101112131415161718191a1b1c1d1e1f",
"a0a1a2a3a4a5a6a7a8a9aaabacadaeaf",
"0ecd9f09ddfc6b7bcad2646fa6bc10f922e5489a4ea755ec87ec1b7df379" +
"85ca",
"b97b1b4ce098f8e22f2f38b60d9f7a0e5902a1193602a876c010d73009dd" +
"0701",
"646e0175bcef43b9ebd2a3884699ad40b34d4b011e91679c5f25f0721d36" +
"7f6a");
}
private static void test_AES_HKDFWithHmacSHA384() {
executeTest("AES - HKDF-SHA384",
"HKDF-SHA384",
"AES",
"000102030405060708090a0b0c0d0e0f",
"101112131415161718191a1b1c1d1e1f",
"a0a1a2a3a4a5a6a7a8a9aaabacadaeaf",
"31ca88a527220f8271d78df4ce6c4d973f135ad37973b96644b4d52d499d" +
"0a2b03d53c875b1176b089e1e6161ab6d92b",
"ba91a67e4d7640495194916ef1252418a651103fbddb0f2ec8b9d1f44f7a" +
"7a0d",
"f3cfcb44d7b36dce96f584c74118b434e714a13448321063241fd24ace11" +
"f2a0");
}
private static void test_AES_HKDFWithHmacSHA512() {
executeTest("AES - HKDF-SHA512",
"HKDF-SHA512",
"AES",
"000102030405060708090a0b0c0d0e0f",
"101112131415161718191a1b1c1d1e1f",
"a0a1a2a3a4a5a6a7a8a9aaabacadaeaf",
"f6e6b1ddb24ea0f0ede0f533d1f350c86bf78966b0e5fd2af34dd00dae39" +
"01d6279fe8111d6572e3cd05f2f0eeabb9144dc0da9437cdf37b0c6d7f3b" +
"1064ab2b",
"302212eb57ae758874e0e52fbdfa4eee29d7c694f181b21d8a8b571a43ce" +
"aad5",
"94459a6593f9c2cfea2ad32970efb8506f3a927927ba283fb6bfd7111aa8" +
"63fc");
}
private static void test_AES_HKDFWithHmacSHA256_EmptyBaseKey() {
executeTest("AES - HKDF-SHA256 (empty base key)",
"HKDF-SHA256",
"AES",
(SecretKey) null,
"101112131415161718191a1b1c1d1e1f",
"a0a1a2a3a4a5a6a7a8a9aaabacadaeaf",
"cc267bd9515c1eba2cf6aaa1fc8380677f4351fcbea6d70873df5a334efc" +
"ee0d",
"cf353a33460b146c0eae3f0788ee281e5a0be15280fbeba107472aa1cd58" +
"d111",
"326e9028f51c05c1919215bad6e35668c94c88040c3777e8e6f8b6acdece" +
"85fa");
}
private static void test_AES_HKDFWithHmacSHA256_EmptyBaseKeySaltInfo() {
executeTest("AES - HKDF-SHA256 (empty base key, salt, and info)",
"HKDF-SHA256",
"AES",
(SecretKey) null,
null,
null,
"b613679a0814d9ec772f95d778c35fc5ff1697c493715653c6c712144292" +
"c5ad",
"eb70f01dede9afafa449eee1b1286504e1f62388b3f7dd4f956697b0e828" +
"fe18",
"3fdcf83994f6e0a6f6f482d097e242355e255a8ed17e661a71ca2d592c7a" +
"884e");
}
private static void test_HKDF_after_DH_HkdfSHA256() throws Exception {
SecretKey tlsPremasterSecret = getTlsPremasterSecretWithDHExchange(
"00bcb8fa0a6b569961782a394599a1a02a05532a836819908a9a9000ed",
"58ceab52f470026eaea24eb250e08d7cc23f21dda57ad628d14eab788633" +
"cebc78c565f9292e6cfe9910d51c4878f590c46cbf380e19acf55cd468ab" +
"672afb29c09b7edfd522d034019eadae75ea99bacf1e166548f092a5d371" +
"930a275cbcb4bb02cb1d1b7a8bf3751dc85e61fb674059deef54e8ebbd36" +
"3bdac4f85c5e49cb7dc8720a8088f047f319a63c2722a720e187f827578b" +
"2545041bb5e640454e791f683622bb5aba4ab9bc51001c59bba5cd6cc0e2" +
"aec00b0a5313a27454a93d3bd3f2ae5ab1c13165d1564e3b2d60629302b3" +
"6bf44c1991bad279d3bd51b142294007f0c8828c9060d8b9b4cc6d335bcc" +
"ce31d4e6aa18fd3ce99cb92aec09de2d",
"00ffffffffffffffffc90fdaa22168c234c4c6628b80dc1cd129024e088a" +
"67cc74020bbea63b139b22514a08798e3404ddef9519b3cd3a431b302b0a" +
"6df25f14374fe1356d6d51c245e485b576625e7ec6f44c42e9a637ed6b0b" +
"ff5cb6f406b7edee386bfb5a899fa5ae9f24117c4b1fe649286651ece45b" +
"3dc2007cb8a163bf0598da48361c55d39a69163fa8fd24cf5f83655d23dc" +
"a3ad961c62f356208552bb9ed529077096966d670c354e4abc9804f1746c" +
"08ca18217c32905e462e36ce3be39e772c180e86039b2783a2ec07a28fb5" +
"c55df06f4c52c9de2bcbf6955817183995497cea956ae515d2261898fa05" +
"1015728e5a8aacaa68ffffffffffffffff",
"02"
);
executeTest("Test HKDF-SHA256 after DH exchange (TLS)",
"HKDF-SHA256",
"Generic",
tlsPremasterSecret,
null,
null,
"e3cf8e5e0892ad251a5863c7f6ddc4fb988b1a723a30d3fe1ac235799caf" +
"86e1",
"86e508974080cdad9fa4407e253d35ae48f40e0e266c91dd04c775538c17" +
"0eacd71bb4d54ba0c5065091",
"2e94c8c852d318887fa94dac544c369bc25879efd39683a9dc5eda55f565" +
"88c0");
}
private static void test_HKDF_after_ECDH_HkdfSHA256() throws Exception {
SecretKey tlsPremasterSecret = getTlsPremasterSecretWithECDHExchange(
"312092587041182431404764856027482553256569653405119712868911" +
"21589605635583946",
"851398477998049170325388348439523125186814652510003800309225" +
"34333070929362623",
"531080873930420952237875954830357399317339863932672261700603" +
"26242234504331049");
executeTest("Test HKDF-SHA256 after ECDH exchange (TLS)",
"HKDF-SHA256",
"Generic",
tlsPremasterSecret,
null,
null,
"638d8874237f12e42b366090ee8a0207d28a1ac8fd12b6a753ecb58c31cd" +
"6a5e",
"348a1afabe9560d3a0a6577e8bd66f0e8dc43b4ad52037f692ea5d28fbb2" +
"bc963ef59eba65a83befc465",
"bab55b2106b4fee07b7afc905ed7c1e84889e941fbc12f132c706addcfc0" +
"6e09");
}
public void main(Provider p) throws Exception {
p11Provider = p;
p11GenericSkf = SecretKeyFactory.getInstance("Generic", p11Provider);
for (Method m : TestHKDF.class.getDeclaredMethods()) {
if (m.getName().startsWith("test")) {
m.invoke(null);
}
}
if (DEBUG || testFailed) {
debugPrinter.flush();
System.out.println(debugOut);
}
if (testFailed) {
throw new Exception("TEST FAILED");
}
System.out.println("TEST PASS - OK");
}
public static void main(String[] args) throws Exception {
main(new TestHKDF(), args);
}
}