RFR: 8328119: Support HKDF in SunPKCS11 (Preview) [v10]
Valerie Peng
valeriep at openjdk.org
Sat Jan 11 01:44:02 UTC 2025
On Wed, 8 Jan 2025 02:31:38 GMT, Martin Balao <mbalao at openjdk.org> wrote:
>> We would like to propose an implementation of the HKDF algorithms for SunPKCS11, aligned with the KDF API proposed for JDK 24 (see [JEP 478: Key Derivation Function API (Preview)](https://bugs.openjdk.org/browse/JDK-8189808)).
>>
>> This implementation will be under the _Preview_ umbrella until the KDF API becomes stable in a future JDK release. The benefit of this early proposal is to gather more feedback about the KDF API for future improvements.
>>
>> The `P11KDF` class has the core implementation and Java calls to the PKCS 11 API. Different native mechanism were used to merge key material: CKM_CONCATENATE_BASE_AND_DATA (key and data), CKM_CONCATENATE_BASE_AND_KEY (key and key) and CKM_CONCATENATE_DATA_AND_BASE (data and key). The implementation also supports merging data with data, at the Java level. List of HKDF algorithms supported: HKDF-SHA256, HKDF-SHA384, and, HKDF-SHA512.
>>
>> Derivation modes supported: extract, expand, and, extract-expand.
>>
>> We further advanced the consolidation of algorithm and key info in the P11SecretKeyFactory map —this effort started with the PBE support enhancement and has helped to avoid duplication—. The map has now information about HMAC (`HMACKeyInfo` class) and HKDF (`HKDFKeyInfo` class) algorithms. P11Mac is now aligned to take the information from the map.
>>
>> Generic keys now supported in SecretKeyFactory. Derived keys could be Generic.
>>
>> Testing
>>
>> * [TestHKDF.java](https://github.com/openjdk/jdk/blob/e87ec99b90ff742f531a5031fdeeb9f2e039856d/test/jdk/sun/security/pkcs11/KDF/TestHKDF.java) test added
>> * All non-SHA1 & non-SHA224 RFC 5869 test vectors checked
>> * Cross-checking against SunJCE's HKDF implementation for every algorithm possible
>> * Static assertion data for resilience if SunJCE were not available
>> * Use of derived key for encryption check
>> * Concatenation of input key material and salt checked (multiple combinations)
>> * Multiple derivation types checked (extract only, expand only, and, extract-expand)
>> * Derive key and derive data checked
>> * All supported HKDF algorithms tested (HKDF-SHA256, HKDF-SHA384, and, HKDF-SHA512)
>> * DH and ECDH key derivation for TLS checked
>> * Informative output for debugging purposes (shown automatically if there is a test failure)
>> * Note: test failures do not prevent all tests for running
>> * Test integrated to the SunPKCS11 tests framework
>>
>> * No regressions observed in jdk/sun/security/pkcs11 (114 tests pa...
>
> Martin Balao has updated the pull request incrementally with one additional commit since the last revision:
>
> Renaming of P11KDF fix.
>
> Co-authored-by: Martin Balao Alonso <mbalao at redhat.com>
> Co-authored-by: Francisco Ferrari Bihurriet <fferrari at redhat.com>
src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11HKDF.java line 230:
> 228: }
> 229:
> 230: private abstract sealed class KeyMaterialMerger permits
Hmm, instead of all these different sub-classes which handle the various combination of key/data when merging the key materials, how about encapsulate the key/data into a record and handling the merging in one class? Something like:
record KeyMaterial(boolean isBytes, byte[] data, P11Key.P11SecretKey key) {
}
private final class KeyMaterialMerger {
private KeyMaterial curr;
private KeyMaterial toKeyMaterial(SecretKey key) {
if (key instanceof SecretKeySpec) {
return new KeyMaterial(true, key.getEncoded(), null);
} else {
return new KeyMaterial(false, null,
convertKey(key, "Failure merging key material"));
}
}
KeyMaterialMerger merge(SecretKey nextKey) {
if (curr == null) {
curr = toKeyMaterial(nextKey);
} else {
KeyMaterial next = toKeyMaterial(nextKey);
int combination = (curr.isBytes ? 2 : 0) |
(next.isBytes ? 1 : 0);
switch (combination) {
case 3: // 'curr' and 'next' both are byte[]
int newLen = curr.data.length + next.data.length;
var resData = Arrays.copyOf(curr.data, newLen);
System.arraycopy(next.data, 0, resData, newLen -
next.data.length, next.data.length);
curr = new KeyMaterial(true, resData, null);
break;
case 2: // 'curr' is byte[], 'next' is key
var resKey = p11Merge(next.key, new CK_MECHANISM(
CKM_CONCATENATE_DATA_AND_BASE,
new CK_KEY_DERIVATION_STRING_DATA(curr.data)),
curr.data.length + next.key.keyLength);
curr = new KeyMaterial(false, null, resKey);
break;
case 1: // 'curr' is key, 'next' is byte[]
resKey = p11Merge(curr.key, new CK_MECHANISM(
CKM_CONCATENATE_BASE_AND_DATA,
new CK_KEY_DERIVATION_STRING_DATA(next.data)),
curr.key.keyLength + next.data.length);
curr = new KeyMaterial(false, null, resKey);
break;
case 0: // 'curr' and 'next' both are key
resKey = p11Merge(curr.key, new CK_MECHANISM(
CKM_CONCATENATE_BASE_AND_KEY, next.key.getKeyID()),
curr.key.keyLength + next.key.keyLength);
curr = new KeyMaterial(false, null, resKey);
break;
default: // should not happen
throw new ProviderException("internal error");
}
}
return this;
}
SecretKey getKeyMaterial() {
if (curr == null) {
return EMPTY_KEY;
} else if (curr.isBytes) {
return new SecretKeySpec(curr.data, "Generic");
} else {
return curr.key;
}
}
private final P11Key.P11SecretKey p11Merge(
P11Key.P11SecretKey baseKey, CK_MECHANISM ckMech,
int derivedKeyLen) {
.... // same no changes here
}
The calling code is almost the same, e.g.
private SecretKey consolidateKeyMaterial(List<SecretKey> keys) {
KeyMaterialMerger keyMerger = new KeyMaterialMerger();
for (SecretKey key : keys) {
keyMerger = keyMerger.merge(key);
}
return keyMerger.getKeyMaterial();
}
-------------
PR Review Comment: https://git.openjdk.org/jdk/pull/22215#discussion_r1911795253
More information about the security-dev
mailing list