forked from openjdk/jdk
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
8328119: Support HKDF in SunPKCS11 (Preview)
8346720: Support Generic keys in SunPKCS11 SecretKeyFactory Co-authored-by: Francisco Ferrari Bihurriet <[email protected]> Co-authored-by: Martin Balao <[email protected]> Reviewed-by: valeriep, kdriver, weijun
- Loading branch information
1 parent
28e744d
commit 6ddbcc3
Showing
16 changed files
with
1,582 additions
and
66 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
367 changes: 367 additions & 0 deletions
367
src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11HKDF.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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(); | ||
} | ||
} |
Oops, something went wrong.