Skip to content

Commit

Permalink
8328119: SunPKCS#11 implementation of HKDF
Browse files Browse the repository at this point in the history
Co-authored-by: Martin Balao Alonso <[email protected]>
Co-authored-by: Francisco Ferrari Bihurriet <[email protected]>
  • Loading branch information
martinuy and franferrax committed Nov 18, 2024
1 parent d52d136 commit e87ec99
Show file tree
Hide file tree
Showing 16 changed files with 1,616 additions and 60 deletions.
1 change: 1 addition & 0 deletions src/java.base/share/classes/module-info.java
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 4 additions & 1 deletion src/jdk.crypto.cryptoki/share/classes/module-info.java
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -23,6 +23,8 @@
* questions.
*/

import jdk.internal.javac.ParticipatesInPreview;

/**
* Provides the implementation of the SunPKCS11 security provider.
*
Expand All @@ -31,6 +33,7 @@
* @moduleGraph
* @since 9
*/
@ParticipatesInPreview
module jdk.crypto.cryptoki {
provides java.security.Provider with sun.security.pkcs11.SunPKCS11;
}
340 changes: 340 additions & 0 deletions src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11KDF.java
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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
Loading

0 comments on commit e87ec99

Please sign in to comment.