Repository navigation
Description
I've been working on enabling ML-KEM in python-pkcs11. That project tests against SoftHSMv2 and opencryptoki. Unfortunately both of these implementations disagree on the handling of CKA_VALUE_LEN when calling C_DecapsulateKey, and after digging a little deeper, I think SoftHSMv2 is in the wrong here.
I think there are actually two bugs in play here.
Bug 1: C_DecapsulateKey rejects CKA_VALUE_LEN with CKR_ATTRIBUTE_READ_ONLY
C_DecapsulateKey calls CreateObject with OBJECT_OP_UNWRAP, which forbids setting CKA_VALUE_LEN:
Line 8765 in 3129355
| rv = this->CreateObject(hSession, secretAttribs, secretAttribsCount, phKey, OBJECT_OP_UNWRAP); |
SoftHSMv2/src/lib/P11Attributes.cpp
Lines 2307 to 2313 in 3129355
| CK_RV P11AttrValueLen::updateAttr(Token* /*token*/, bool /*isPrivate*/, CK_VOID_PTR pValue, CK_ULONG ulValueLen, int op) | |
| { | |
| // Attribute specific checks | |
| if (op != OBJECT_OP_GENERATE && op != OBJECT_OP_DERIVE) | |
| { | |
| return CKR_ATTRIBUTE_READ_ONLY; |
SoftHSMv2 seemingly assumes that the intended output length is 32 bytes (i.e. the natural output length of the ML-KEM decapsulate operation) and returns that unconditionally.
Assuming the semantics of C_UnwrapKey in the spec also apply to C_DecapsulateKey, I believe CKA_VALUE_LEN should be allowed, and it's actually the only way to unwrap a key that is less than 32 bytes long.
Bug 2: C_EncapsulateKey stores a CKA_VALUE_LEN that disagrees with the actual key material
The created object inherits CKA_VALUE_LEN from the caller template, but at the same time, the byteLen in this code is initialised to 32 and never updated, so this branch is never taken:
Lines 8519 to 8521 in 3129355
| // Truncate value when requested, remove from the leading end | |
| if (byteLen < secretValue.size()) | |
| secretValue.resize(byteLen); |
Expected behaviour
Unlike C_UnwrapKey, C_DecapsulateKey doesn't take a key length parameter, only a ciphertext length, so CKA_VALUE_LEN is the only way to convey the expected plaintext length.
I think this is the way things should work given what the spec says:
CKA_VALUE_LENabsent: produce a key of the full 32-byte ML-KEM output.CKA_VALUE_LENpresent, value <= 32: truncate the key material to that length; bothCKA_VALUEand theCKA_VALUE_LENattribute on the resulting object must reflect the truncated size.CKA_VALUE_LENpresent, value > 32: returnCKR_WRAPPED_KEY_LEN_RANGEas required by the spec.
One could possibly also validate whether the resulting length makes sense for the mechanism requested (e.g. limit AES keys to 16, 24 or 32 bytes). As it happens, opencryptoki does this, and that's how I found out about this issue: the way the check is implemented over there errors out if CKA_VALUE_LEN is not present in the template.
I hope this makes sense.
Activity
[-]Strange `CKA_VALUE_LEN` handling in key encapsulation[/-][+]Strange `CKA_VALUE_LEN` handling in key encapsulation (ML-KEM)[/+]bukka commentedon Jun 21, 2026
CC @antoinelochet
antoinelochet commentedon Jun 21, 2026
Hello,
I see your point and did not think about the CKA_VALUE_LEN attribute at all when developing the feature.
I concur with your analysis and will take CKA_VALUE_LEN into account when decapsulating.
antoinelochet commentedon Jun 21, 2026
@MatthiasValvekens You can try a fix with the branch here: #888
MatthiasValvekens commentedon Jun 21, 2026
Thanks, that seems to work! My tests with
key_length=256andkey_length=128pass.There's another issue related to the encapsulation / decapsulation endpoint not handling
pCipherText == NULLcorrectly (that indicates a probe request). I'll file a separate bug report for that one.EDIT: See #889.
Added possibility to set CKA_VALUE_LEN on C_Encapsulate/C_Decapsulate (…
MatthiasValvekens commentedon Jun 28, 2026
Fixed in #888, so closing this.