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: SunPKCS#11 implementation of HKDF
Co-authored-by: Martin Balao Alonso <[email protected]> Co-authored-by: Francisco Ferrari Bihurriet <[email protected]>
- Loading branch information
1 parent
d52d136
commit e87ec99
Showing
16 changed files
with
1,616 additions
and
60 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
340 changes: 340 additions & 0 deletions
340
src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11KDF.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,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> T derive(String alg, AlgorithmParameterSpec derivationSpec, | ||
Class<T> 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<SecretKey> keys) { | ||
KeyMaterialMerger keyMerger = P11KDF.this.new AnyKeyMaterialMerger(); | ||
for (SecretKey key : keys) { | ||
keyMerger = keyMerger.merge(key); | ||
} | ||
return keyMerger.getKeyMaterial(); | ||
} | ||
} |
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
Oops, something went wrong.