From e87ec99b90ff742f531a5031fdeeb9f2e039856d Mon Sep 17 00:00:00 2001 From: Martin Balao Date: Mon, 18 Nov 2024 11:17:17 -0500 Subject: [PATCH] 8328119: SunPKCS#11 implementation of HKDF Co-authored-by: Martin Balao Alonso Co-authored-by: Francisco Ferrari Bihurriet --- src/java.base/share/classes/module-info.java | 1 + .../share/classes/module-info.java | 5 +- .../classes/sun/security/pkcs11/P11KDF.java | 340 +++++++++ .../classes/sun/security/pkcs11/P11Key.java | 2 +- .../classes/sun/security/pkcs11/P11Mac.java | 35 +- .../security/pkcs11/P11SecretKeyFactory.java | 113 ++- .../sun/security/pkcs11/SunPKCS11.java | 26 + .../pkcs11/wrapper/CK_HKDF_PARAMS.java | 176 +++++ .../CK_KEY_DERIVATION_STRING_DATA.java | 79 ++ .../security/pkcs11/wrapper/CK_MECHANISM.java | 8 + .../security/pkcs11/wrapper/Functions.java | 22 +- .../pkcs11/wrapper/PKCS11Constants.java | 6 +- .../share/native/libj2pkcs11/p11_convert.c | 153 ++++ .../share/native/libj2pkcs11/p11_util.c | 10 + .../share/native/libj2pkcs11/pkcs11wrapper.h | 6 +- .../jdk/sun/security/pkcs11/KDF/TestHKDF.java | 694 ++++++++++++++++++ 16 files changed, 1616 insertions(+), 60 deletions(-) create mode 100644 src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11KDF.java create mode 100644 src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/wrapper/CK_HKDF_PARAMS.java create mode 100644 src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/wrapper/CK_KEY_DERIVATION_STRING_DATA.java create mode 100644 test/jdk/sun/security/pkcs11/KDF/TestHKDF.java diff --git a/src/java.base/share/classes/module-info.java b/src/java.base/share/classes/module-info.java index 828e0d415040c..2ffc7c41d12aa 100644 --- a/src/java.base/share/classes/module-info.java +++ b/src/java.base/share/classes/module-info.java @@ -158,6 +158,7 @@ java.desktop, // for ScopedValue java.se, // for ParticipatesInPreview jdk.compiler, + jdk.crypto.cryptoki, // participates in preview features jdk.incubator.vector, // participates in preview features jdk.jartool, // participates in preview features jdk.jdeps, // participates in preview features diff --git a/src/jdk.crypto.cryptoki/share/classes/module-info.java b/src/jdk.crypto.cryptoki/share/classes/module-info.java index 57388fb8851a7..687abc5b0056e 100644 --- a/src/jdk.crypto.cryptoki/share/classes/module-info.java +++ b/src/jdk.crypto.cryptoki/share/classes/module-info.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 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 @@ -23,6 +23,8 @@ * questions. */ +import jdk.internal.javac.ParticipatesInPreview; + /** * Provides the implementation of the SunPKCS11 security provider. * @@ -31,6 +33,7 @@ * @moduleGraph * @since 9 */ +@ParticipatesInPreview module jdk.crypto.cryptoki { provides java.security.Provider with sun.security.pkcs11.SunPKCS11; } diff --git a/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11KDF.java b/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11KDF.java new file mode 100644 index 0000000000000..83eff1d3db707 --- /dev/null +++ b/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11KDF.java @@ -0,0 +1,340 @@ +/* + * Copyright (c) 2024, 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 P11KDF extends KDFSpi { + private final Token token; + private final P11SecretKeyFactory.HKDFKeyInfo svcKi; + private final long hmacMechanism; + + private static KDFParameters requireNull(KDFParameters kdfParameters, + String message) throws InvalidAlgorithmParameterException { + if (kdfParameters != null) { + throw new InvalidAlgorithmParameterException(message); + } + return null; + } + + P11KDF(Token token, String algorithm, KDFParameters kdfParameters, + long hmacMechanism) throws InvalidAlgorithmParameterException { + super(requireNull(kdfParameters, + algorithm + " does not support parameters")); + this.token = token; + this.hmacMechanism = hmacMechanism; + this.svcKi = P11SecretKeyFactory.getHKDFKeyInfo(algorithm); + assert this.svcKi != null : "Only HKDF algorithms supported."; + } + + @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 derive(String alg, AlgorithmParameterSpec derivationSpec, + Class retType) throws InvalidAlgorithmParameterException { + SecretKey baseKey; + SecretKey salt = null; + 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."; + + 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 != null) { + // 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, + hmacMechanism, 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(isData ? CKM_HKDF_DATA : CKM_HKDF_DERIVE, + 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, + 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) { + 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, 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 P11KDF.this.new DataKeyMaterialMerger(nextKeyMaterial); + } + + protected KeyMaterialMerger merge(P11Key.P11SecretKey nextKeyMaterial) { + return P11KDF.this.new KeyKeyMaterialMerger(nextKeyMaterial); + } + + SecretKey getKeyMaterial() { + return null; + } + } + + 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 P11KDF.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 keys) { + KeyMaterialMerger keyMerger = P11KDF.this.new AnyKeyMaterialMerger(); + for (SecretKey key : keys) { + keyMerger = keyMerger.merge(key); + } + return keyMerger.getKeyMaterial(); + } +} diff --git a/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11Key.java b/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11Key.java index 5d83fa106fd90..19699f6b5378a 100644 --- a/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11Key.java +++ b/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11Key.java @@ -446,7 +446,7 @@ byte[] getEncodedInternal() { } } - private static class P11SecretKey extends P11Key implements SecretKey { + static class P11SecretKey extends P11Key implements SecretKey { @Serial private static final long serialVersionUID = -7828241727014329084L; diff --git a/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11Mac.java b/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11Mac.java index ee90e8bab6fbc..d5f3da36a79dc 100644 --- a/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11Mac.java +++ b/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11Mac.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 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 @@ -90,31 +90,24 @@ final class P11Mac extends MacSpi { // one byte buffer for the update(byte) method, initialized on demand private byte[] oneByte; - P11Mac(Token token, String algorithm, long mechanism) - throws PKCS11Exception { + P11Mac(Token token, String algorithm, long mechanism) { super(); this.token = token; this.algorithm = algorithm; this.svcPbeKi = P11SecretKeyFactory.getPBEKeyInfo(algorithm); - Long params = null; - macLength = switch ((int) mechanism) { - case (int) CKM_MD5_HMAC -> 16; - case (int) CKM_SHA_1_HMAC -> 20; - case (int) CKM_SHA224_HMAC, (int) CKM_SHA512_224_HMAC, (int) CKM_SHA3_224_HMAC -> 28; - case (int) CKM_SHA256_HMAC, (int) CKM_SHA512_256_HMAC, (int) CKM_SHA3_256_HMAC -> 32; - case (int) CKM_SHA384_HMAC, (int) CKM_SHA3_384_HMAC -> 48; - 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 -> { - params = Long.valueOf(20); - yield 20; + if (svcPbeKi != null) { + macLength = svcPbeKi.keyLen / 8; + } else { + P11SecretKeyFactory.HMACKeyInfo svcKi = + P11SecretKeyFactory.getHMACKeyInfo(algorithm); + if (svcKi == null) { + throw new ProviderException("Unknown mechanism: " + mechanism); } - default -> throw new ProviderException("Unknown mechanism: " + mechanism); - }; - ckMechanism = new CK_MECHANISM(mechanism, params); + macLength = svcKi.keyLen / 8; + } + ckMechanism = new CK_MECHANISM(mechanism, mechanism == CKM_SSL3_MD5_MAC + || mechanism == CKM_SSL3_SHA1_MAC ? Long.valueOf(macLength) : + null); } // reset the states to the pre-initialized values diff --git a/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11SecretKeyFactory.java b/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11SecretKeyFactory.java index 64a05a5cacf12..7f8dbc067f88a 100644 --- a/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11SecretKeyFactory.java +++ b/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11SecretKeyFactory.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 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 @@ -67,21 +67,12 @@ final class P11SecretKeyFactory extends SecretKeyFactorySpi { } private static final Map 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) { KeyInfo ki = keyInfo.get(algo); if (ki == null) { String algoUpper = algo.toUpperCase(Locale.ENGLISH); ki = keyInfo.get(algoUpper); - if (ki == null) { - if (algoUpper.startsWith("HMAC")) { - return HMAC; - } else if (algoUpper.startsWith("SSLMAC")) { - return SSLMAC; - } - } } return ki; } @@ -93,12 +84,26 @@ static PBEKeyInfo getPBEKeyInfo(String algo) { 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) { keyInfo.put(ki.algo, 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 long keyType; @@ -129,6 +134,27 @@ static boolean checkUse(KeyInfo ki, KeyInfo si) { } } + 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 int prkLen; + + HKDFKeyInfo(String algo, HMACKeyInfo hmacKi) { + super(algo, UNKNOWN_KEY_TYPE); + prkLen = hmacKi.keyLen; + } + } + abstract static sealed class PBEKeyInfo extends KeyInfo permits AESPBEKeyInfo, PBKDF2KeyInfo, P12MacPBEKeyInfo { public static final long INVALID_PRF = -1; @@ -169,9 +195,9 @@ static final class P12MacPBEKeyInfo extends PBEKeyInfo { private static final CK_ATTRIBUTE[] attr = new CK_ATTRIBUTE[] { CK_ATTRIBUTE.SIGN_TRUE}; - P12MacPBEKeyInfo(String algo, long kdfMech, int keyLen) { + P12MacPBEKeyInfo(String algo, long kdfMech, HMACKeyInfo hmacKi) { super(algo, kdfMech, PBEKeyInfo.INVALID_PRF, - CKK_GENERIC_SECRET, keyLen, attr); + CKK_GENERIC_SECRET, hmacKi.keyLen, attr); } } @@ -195,6 +221,38 @@ static final class P12MacPBEKeyInfo extends PBEKeyInfo { putKeyInfo(new KeyInfo("TlsMasterSecret", PCKK_TLSMASTER)); 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("HKDFWithHmacSHA1", hmacSHA1)); + putKeyInfo(new HKDFKeyInfo("HKDFWithHmacSHA224", hmacSHA224)); + putKeyInfo(new HKDFKeyInfo("HKDFWithHmacSHA256", hmacSHA256)); + putKeyInfo(new HKDFKeyInfo("HKDFWithHmacSHA384", hmacSHA384)); + putKeyInfo(new HKDFKeyInfo("HKDFWithHmacSHA512", hmacSHA512)); + putKeyInfo(new AESPBEKeyInfo("PBEWithHmacSHA1AndAES_128", CKP_PKCS5_PBKD2_HMAC_SHA1, 128)); putKeyInfo(new AESPBEKeyInfo("PBEWithHmacSHA224AndAES_128", @@ -228,15 +286,15 @@ static final class P12MacPBEKeyInfo extends PBEKeyInfo { CKP_PKCS5_PBKD2_HMAC_SHA512)); putKeyInfo(new P12MacPBEKeyInfo("HmacPBESHA1", - CKM_PBA_SHA1_WITH_SHA1_HMAC, 160)); + CKM_PBA_SHA1_WITH_SHA1_HMAC, hmacSHA1)); 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", - CKM_NSS_PKCS12_PBE_SHA256_HMAC_KEY_GEN, 256)); + CKM_NSS_PKCS12_PBE_SHA256_HMAC_KEY_GEN, hmacSHA256)); 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", - CKM_NSS_PKCS12_PBE_SHA512_HMAC_KEY_GEN, 512)); + CKM_NSS_PKCS12_PBE_SHA512_HMAC_KEY_GEN, hmacSHA512)); } // No pseudo key types @@ -287,8 +345,7 @@ static P11Key convertKey(Token token, Key key, String svcAlgo, } // Check if the key can be used for the service. // Any key can be used for a MAC service. - if (svcAlgo != keyAlgo && - si.keyType != PCKK_HMAC && si.keyType != PCKK_SSLMAC) { + if (svcAlgo != keyAlgo && !(si instanceof HMACKeyInfo)) { ki = getKeyInfo(keyAlgo); if (ki == null || !KeyInfo.checkUse(ki, si)) { throw new InvalidKeyException("Cannot use a " + keyAlgo + @@ -348,8 +405,7 @@ static P11Key convertKey(Token token, Key key, String svcAlgo, } byte[] encoded = key.getEncoded(); try { - p11Key = createKey(token, encoded, svcAlgo, si.keyType, - extraAttrs); + p11Key = createKey(token, encoded, svcAlgo, si, extraAttrs); } finally { Arrays.fill(encoded, (byte) 0); } @@ -486,10 +542,11 @@ static void fixDESParity(byte[] key, int offset) { } private static P11Key createKey(Token token, byte[] encoded, - String algorithm, long keyType, CK_ATTRIBUTE[] extraAttrs) + String algorithm, KeyInfo ki, CK_ATTRIBUTE[] extraAttrs) throws InvalidKeyException { int n = encoded.length << 3; int keyLength = n; + long keyType = ki.keyType; try { switch ((int) keyType) { case (int) CKK_DES -> { @@ -520,16 +577,12 @@ private static P11Key createKey(Token token, byte[] encoded, CKM_CHACHA20_KEY_GEN, n, token); case (int) CKK_GENERIC_SECRET, (int) PCKK_TLSPREMASTER, (int) PCKK_TLSRSAPREMASTER, (int) PCKK_TLSMASTER -> 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 " + algorithm); } + if (ki instanceof HMACKeyInfo && n == 0) { + throw new InvalidKeyException("MAC keys must not be empty"); + } } catch (InvalidAlgorithmParameterException iape) { throw new InvalidKeyException("Invalid key for " + algorithm, iape); diff --git a/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/SunPKCS11.java b/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/SunPKCS11.java index 19d56dec93c30..ce29c94ad641d 100644 --- a/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/SunPKCS11.java +++ b/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/SunPKCS11.java @@ -31,6 +31,7 @@ import java.security.*; import java.security.interfaces.*; +import javax.crypto.KDFParameters; import javax.crypto.interfaces.*; import javax.security.auth.Subject; @@ -526,6 +527,8 @@ private static void register(Descriptor d) { private static final String SR = "SecureRandom"; + private static final String KDF = "KDF"; + static { // names of all the implementation classes // use local variables, only used here @@ -546,6 +549,7 @@ private static void register(Descriptor d) { String P11PBECipher = "sun.security.pkcs11.P11PBECipher"; String P11Signature = "sun.security.pkcs11.P11Signature"; String P11PSSSignature = "sun.security.pkcs11.P11PSSSignature"; + String P11KDF = "sun.security.pkcs11.P11KDF"; // XXX register all aliases @@ -729,6 +733,8 @@ private static void register(Descriptor d) { m(CKM_BLOWFISH_CBC)); d(SKF, "ChaCha20", P11SecretKeyFactory, m(CKM_CHACHA20_POLY1305)); + d(SKF, "Generic", P11SecretKeyFactory, + m(CKM_GENERIC_SECRET_KEY_GEN)); /* * PBE Secret Key Factories @@ -1073,6 +1079,17 @@ private static void register(Descriptor d) { m(CKM_TLS_PRF, CKM_NSS_TLS_PRF_GENERAL)); d(KG, "SunTls12Prf", "sun.security.pkcs11.P11TlsPrfGenerator", m(CKM_TLS_MAC)); + + d(KDF, "HKDFWithHmacSHA1", P11KDF, m(CKM_SHA_1_HMAC), + m(CKM_HKDF_DERIVE, CKM_HKDF_DATA)); + d(KDF, "HKDFWithHmacSHA224", P11KDF, m(CKM_SHA224_HMAC), + m(CKM_HKDF_DERIVE, CKM_HKDF_DATA)); + d(KDF, "HKDFWithHmacSHA256", P11KDF, m(CKM_SHA256_HMAC), + m(CKM_HKDF_DERIVE, CKM_HKDF_DATA)); + d(KDF, "HKDFWithHmacSHA384", P11KDF, m(CKM_SHA384_HMAC), + m(CKM_HKDF_DERIVE, CKM_HKDF_DATA)); + d(KDF, "HKDFWithHmacSHA512", P11KDF, m(CKM_SHA512_HMAC), + m(CKM_HKDF_DERIVE, CKM_HKDF_DATA)); } // background thread that periodically checks for token insertion @@ -1507,6 +1524,15 @@ public Object newInstance0(Object param) throws throw new NoSuchAlgorithmException("Unsupported algorithm: " + algorithm); } + } else if (type == KDF) { + try { + return new P11KDF(token, algorithm, (KDFParameters) param, + mechanism); + } catch (ClassCastException | + InvalidAlgorithmParameterException e) { + throw new NoSuchAlgorithmException( + "Cannot instantiate a service of KDF type.", e); + } } else { throw new NoSuchAlgorithmException("Unknown type: " + type); } diff --git a/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/wrapper/CK_HKDF_PARAMS.java b/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/wrapper/CK_HKDF_PARAMS.java new file mode 100644 index 0000000000000..4fd9471e9fd89 --- /dev/null +++ b/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/wrapper/CK_HKDF_PARAMS.java @@ -0,0 +1,176 @@ +/* + * Copyright (c) 2024, 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; + +import static sun.security.pkcs11.wrapper.PKCS11Constants.*; + +/** + * class CK_HKDF_PARAMS provides the parameters to the CKM_HKDF_DERIVE, + * CKM_HKDF_DATA and CKM_HKDF_KEY_GEN mechanisms.

+ * PKCS#11 structure: + *

+ * 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;
+ * 
+ * + */ +public class CK_HKDF_PARAMS { + + /** + * PKCS#11: + *
+     *   CK_BBOOL bExtract;
+     * 
+ */ + public boolean bExtract; + + /** + * PKCS#11: + *
+     *   CK_BBOOL bExpand;
+     * 
+ */ + public boolean bExpand; + + /** + * PKCS#11: + *
+     *   CK_MECHANISM_TYPE prfHashMechanism;
+     * 
+ */ + public long prfHashMechanism; + + /** + * PKCS#11: + *
+     *   CK_ULONG ulSaltType;
+     * 
+ */ + public long ulSaltType; + + /** + * PKCS#11: + *
+     *   CK_BYTE_PTR pSalt;
+     *   CK_ULONG ulSaltLen;
+     * 
+ */ + public byte[] pSalt; + + /** + * PKCS#11: + *
+     *   CK_OBJECT_HANDLE hSaltKey;
+     * 
+ */ + public long hSaltKey; + + /** + * PKCS#11: + *
+     *   CK_BYTE_PTR pInfo;
+     *   CK_ULONG ulInfoLen;
+     * 
+ */ + public 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(); + } +} diff --git a/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/wrapper/CK_KEY_DERIVATION_STRING_DATA.java b/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/wrapper/CK_KEY_DERIVATION_STRING_DATA.java new file mode 100644 index 0000000000000..cccb7fe41bbc3 --- /dev/null +++ b/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/wrapper/CK_KEY_DERIVATION_STRING_DATA.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2024, 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.

+ * PKCS#11 structure: + *

+ * typedef struct CK_KEY_DERIVATION_STRING_DATA {
+ *     CK_BYTE_PTR pData;
+ *     CK_ULONG ulLen;
+ * } CK_KEY_DERIVATION_STRING_DATA;
+ * 
+ * + */ +public class CK_KEY_DERIVATION_STRING_DATA { + + /** + * PKCS#11: + *
+     *   CK_BYTE_PTR pData;
+     *   CK_ULONG ulLen;
+     * 
+ */ + public 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(); + } +} diff --git a/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/wrapper/CK_MECHANISM.java b/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/wrapper/CK_MECHANISM.java index cb74f20c3de34..585f1663e7124 100644 --- a/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/wrapper/CK_MECHANISM.java +++ b/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/wrapper/CK_MECHANISM.java @@ -127,6 +127,14 @@ public CK_MECHANISM(long mechanism, CK_TLS_MAC_PARAMS 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) { init(mechanism, params); } diff --git a/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/wrapper/Functions.java b/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/wrapper/Functions.java index 31abd869a767a..3def4c51d5732 100644 --- a/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/wrapper/Functions.java +++ b/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/wrapper/Functions.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2024, Oracle and/or its affiliates. All rights reserved. */ /* Copyright (c) 2002 Graz University of Technology. All rights reserved. @@ -440,6 +440,26 @@ public static String mechanismInfoFlagsToString(long 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 nameMap, long id) { String name = null; if ((id >>> 32) == 0) { diff --git a/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/wrapper/PKCS11Constants.java b/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/wrapper/PKCS11Constants.java index 43fda2011bb03..603b2f19196fe 100644 --- a/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/wrapper/PKCS11Constants.java +++ b/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/wrapper/PKCS11Constants.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2024, Oracle and/or its affiliates. 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) 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_TLSRSAPREMASTER = 0x7FFFFF26L; public static final long PCKK_TLSMASTER = 0x7FFFFF27L; @@ -1156,11 +1154,11 @@ public interface PKCS11Constants { = 0x00000001L; public static final long CK_SP800_108_DKM_LENGTH_SUM_OF_SEGMENTS = 0x00000002L; + */ 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_KEY = 0x00000004L; - */ // private NSS attribute (for DSA and DH private keys) public static final long CKA_NETSCAPE_DB = 0xD5A0DB00L; diff --git a/src/jdk.crypto.cryptoki/share/native/libj2pkcs11/p11_convert.c b/src/jdk.crypto.cryptoki/share/native/libj2pkcs11/p11_convert.c index 986ef9799c459..69717513a7524 100644 --- a/src/jdk.crypto.cryptoki/share/native/libj2pkcs11/p11_convert.c +++ b/src/jdk.crypto.cryptoki/share/native/libj2pkcs11/p11_convert.c @@ -731,6 +731,150 @@ jTlsMacParamsToCKTlsMacParamPtr(JNIEnv *env, jobject jParam, CK_ULONG *pLength) 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, jclass jKeyMatParamClass, CK_ULONG* cKKeyMatParamUlMacSizeInBits, @@ -1497,6 +1641,15 @@ CK_VOID_PTR jMechParamToCKMechParamPtrSlow(JNIEnv *env, jobject jParam, ckpParamPtr = jTlsMacParamsToCKTlsMacParamPtr(env, jParam, ckpLength); 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: ckpParamPtr = jAesCtrParamsToCKAesCtrParamPtr(env, jParam, ckpLength); diff --git a/src/jdk.crypto.cryptoki/share/native/libj2pkcs11/p11_util.c b/src/jdk.crypto.cryptoki/share/native/libj2pkcs11/p11_util.c index 4edf9c94ce294..a2e2f8d964b7e 100644 --- a/src/jdk.crypto.cryptoki/share/native/libj2pkcs11/p11_util.c +++ b/src/jdk.crypto.cryptoki/share/native/libj2pkcs11/p11_util.c @@ -355,6 +355,16 @@ void freeCKMechanismPtr(CK_MECHANISM_PTR mechPtr) { free(((CK_TLS_PRF_PARAMS*)tmp)->pulOutputLen); free(((CK_TLS_PRF_PARAMS*)tmp)->pOutput); 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_TLS_MASTER_KEY_DERIVE: case CKM_SSL3_MASTER_KEY_DERIVE_DH: diff --git a/src/jdk.crypto.cryptoki/share/native/libj2pkcs11/pkcs11wrapper.h b/src/jdk.crypto.cryptoki/share/native/libj2pkcs11/pkcs11wrapper.h index 577f5f20d2cb1..8c73221bd3f50 100644 --- a/src/jdk.crypto.cryptoki/share/native/libj2pkcs11/pkcs11wrapper.h +++ b/src/jdk.crypto.cryptoki/share/native/libj2pkcs11/pkcs11wrapper.h @@ -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_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_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" // 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" @@ -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_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_HKDF_PARAMS "sun/security/pkcs11/wrapper/CK_HKDF_PARAMS" /* function to update the CK_NSS_GCM_PARAMS in mechanism pointer with * 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_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_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_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); diff --git a/test/jdk/sun/security/pkcs11/KDF/TestHKDF.java b/test/jdk/sun/security/pkcs11/KDF/TestHKDF.java new file mode 100644 index 0000000000000..996e722c5e4e0 --- /dev/null +++ b/test/jdk/sun/security/pkcs11/KDF/TestHKDF.java @@ -0,0 +1,694 @@ +/* + * Copyright (c) 2024, 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 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 AES/CBC/PKCS5Padding and an IV of 16 zero bytes. + // - If derivedKeyAlg is Generic, expectedOpOut is the result of + // calculating the HmacSHA256 of knownBytes. + } + + 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> 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 keyMaterialCombination, byte[] keyMaterial, + Consumer addKeyCb, Consumer 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 generateKdfParamSpecs( + TestContext ctx, KdfParamSpecType type) throws Exception { + List kdfParamSpecs = new ArrayList<>(); + if (type == KdfParamSpecType.EXTRACT || + type == KdfParamSpecType.EXTRACT_EXPAND) { + for (List 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 { + 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 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 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 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 ikms = null; + List 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", + "HKDFWithHmacSHA256", + "Generic", + "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b", + "000102030405060708090a0b0c", + "f0f1f2f3f4f5f6f7f8f9", + "077709362c2e32df0ddc3f0dc47bba6390b6c73bb50f9c3122ec844ad7c2" + + "b3e5", + "3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4" + + "c5bf34007208d5b887185865", + "ad9e90d0c59d47539899647a3baf0fd364c54eeb5f4d0b80e1f39579e434" + + "e801"); + } + + private static void test_RFC_5869_case_2() { + executeTest("RFC 5869 - Test Case 2", + "HKDFWithHmacSHA256", + "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", + "HKDFWithHmacSHA256", + "Generic", + "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b", + null, + null, + "19ef24a32c717b167f33a91d6f648bdf96596776afdb6377ac434c1c293c" + + "cb04", + "8da4e775a563c18f715f802a063c5a31b8a11f5c5ee1879ec3454e5f3c73" + + "8d2d9d201395faa4b61a96c8", + "06828b5679679681be59aa2822869cb1a174319e53a545e3301bd832ae3e" + + "513f"); + } + + private static void test_RFC_5869_case_4() { + executeTest("RFC 5869 - Test Case 4", + "HKDFWithHmacSHA1", + "Generic", + "0b0b0b0b0b0b0b0b0b0b0b", + "000102030405060708090a0b0c", + "f0f1f2f3f4f5f6f7f8f9", + "9b6c18c432a7bf8f0e71c8eb88f4b30baa2ba243", + "085a01ea1b10f36933068b56efa5ad81a4f14b822f5b091568a9cdd4f155" + + "fda2c22e422478d305f3f896", + "1bc3a8faea5b1a20a4f05f2c687ee23b10122d01649272ea36f2717ae8db" + + "dc4b"); + } + + private static void test_RFC_5869_case_5() { + executeTest("RFC 5869 - Test Case 5", + "HKDFWithHmacSHA1", + "Generic", + "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d" + + "1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b" + + "3c3d3e3f404142434445464748494a4b4c4d4e4f", + "606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d" + + "7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b" + + "9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeaf", + "b0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccd" + + "cecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaeb" + + "ecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff", + "8adae09a2a307059478d309b26c4115a224cfaf6", + "0bd770a74d1160f7c9f12cd5912a06ebff6adcae899d92191fe4305673ba" + + "2ffe8fa3f1a4e5ad79f3f334b3b202b2173c486ea37ce3d397ed034c7f9d" + + "feb15c5e927336d0441f4c4300e2cff0d0900b52d3b4", + "99aeb2e49659f09be1967a476514051eee9707465d46b4c2909747ec2f6b" + + "d4f2"); + } + + private static void test_RFC_5869_case_6() { + executeTest("RFC 5869 - Test Case 6", + "HKDFWithHmacSHA1", + "Generic", + "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b", + null, + null, + "da8c8a73c7fa77288ec6f5e7c297786aa0d32d01", + "0ac1af7002b3d761d1e55298da9d0506b9ae52057220a306e07b6b87e8df" + + "21d0ea00033de03984d34918", + "0775dc9219ac2173e97255d0fd91048e59123ee1a0661ccf0b84b9d501ec" + + "4555"); + } + + private static void test_RFC_5869_case_7() { + executeTest("RFC 5869 - Test Case 7", + "HKDFWithHmacSHA1", + "Generic", + "0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c", + "0000000000000000000000000000000000000000", + null, + "2adccada18779e7c2077ad2eb19d3f3e731385dd", + "2c91117204d745f3500d636a62f64f0ab3bae548aa53d423b0d1f27ebba6" + + "f5e5673a081d70cce7acfc48", + "fe98b5795bb7be4e817c5d45cbda1d6e5102505380c86788bc89fbbbec4b" + + "6ed0"); + } + + private static void test_AES_HKDFWithHmacSHA1() { + executeTest("AES - HKDFWithHmacSHA1", + "HKDFWithHmacSHA1", + "AES", + "000102030405060708090a0b0c0d0e0f", + "101112131415161718191a1b1c1d1e1f", + "a0a1a2a3a4a5a6a7a8a9aaabacadaeaf", + "c3b1e39a13a600685574556ff856a98e455f0f99", + "86760e2a6046b65e823e331af30c575a5670215db95e0cc7e28f5ee4de0b" + + "949c", + "4d4ccd17db01b4b2d957f978fe105fc8496a1e9ae4f9f753077abd81c697" + + "3c67"); + } + + private static void test_AES_HKDFWithHmacSHA224() { + executeTest("AES - HKDFWithHmacSHA224", + "HKDFWithHmacSHA224", + "AES", + "000102030405060708090a0b0c0d0e0f", + "101112131415161718191a1b1c1d1e1f", + "a0a1a2a3a4a5a6a7a8a9aaabacadaeaf", + "0d71c4997aa9f58cc0b15aa223642a5fc3f548cdf79b5904c1da0719", + "c695df4f5928321d7cc88beb013c2039ee78bcd9c0c41ab3157a403344e4" + + "d45b", + "c802027b220363abb086b8b90eee9cee3be0e5ce96d3711706bdfe14e977" + + "0ef0"); + } + + private static void test_AES_HKDFWithHmacSHA256() { + executeTest("AES - HKDFWithHmacSHA256", + "HKDFWithHmacSHA256", + "AES", + "000102030405060708090a0b0c0d0e0f", + "101112131415161718191a1b1c1d1e1f", + "a0a1a2a3a4a5a6a7a8a9aaabacadaeaf", + "0ecd9f09ddfc6b7bcad2646fa6bc10f922e5489a4ea755ec87ec1b7df379" + + "85ca", + "b97b1b4ce098f8e22f2f38b60d9f7a0e5902a1193602a876c010d73009dd" + + "0701", + "646e0175bcef43b9ebd2a3884699ad40b34d4b011e91679c5f25f0721d36" + + "7f6a"); + } + + private static void test_AES_HKDFWithHmacSHA384() { + executeTest("AES - HKDFWithHmacSHA384", + "HKDFWithHmacSHA384", + "AES", + "000102030405060708090a0b0c0d0e0f", + "101112131415161718191a1b1c1d1e1f", + "a0a1a2a3a4a5a6a7a8a9aaabacadaeaf", + "31ca88a527220f8271d78df4ce6c4d973f135ad37973b96644b4d52d499d" + + "0a2b03d53c875b1176b089e1e6161ab6d92b", + "ba91a67e4d7640495194916ef1252418a651103fbddb0f2ec8b9d1f44f7a" + + "7a0d", + "f3cfcb44d7b36dce96f584c74118b434e714a13448321063241fd24ace11" + + "f2a0"); + } + + private static void test_AES_HKDFWithHmacSHA512() { + executeTest("AES - HKDFWithHmacSHA512", + "HKDFWithHmacSHA512", + "AES", + "000102030405060708090a0b0c0d0e0f", + "101112131415161718191a1b1c1d1e1f", + "a0a1a2a3a4a5a6a7a8a9aaabacadaeaf", + "f6e6b1ddb24ea0f0ede0f533d1f350c86bf78966b0e5fd2af34dd00dae39" + + "01d6279fe8111d6572e3cd05f2f0eeabb9144dc0da9437cdf37b0c6d7f3b" + + "1064ab2b", + "302212eb57ae758874e0e52fbdfa4eee29d7c694f181b21d8a8b571a43ce" + + "aad5", + "94459a6593f9c2cfea2ad32970efb8506f3a927927ba283fb6bfd7111aa8" + + "63fc"); + } + + 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 HKDFWithHmacSHA256 after DH exchange (TLS)", + "HKDFWithHmacSHA256", + "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 HKDFWithHmacSHA256 after ECDH exchange (TLS)", + "HKDFWithHmacSHA256", + "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); + } +}