Skip to content

Strange CKA_VALUE_LEN handling in key encapsulation (ML-KEM) #885

Closed

Description

@MatthiasValvekens

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:

rv = this->CreateObject(hSession, secretAttribs, secretAttribsCount, phKey, OBJECT_OP_UNWRAP);

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:

SoftHSMv2/src/lib/SoftHSM.cpp

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_LEN absent: produce a key of the full 32-byte ML-KEM output.
  • CKA_VALUE_LEN present, value <= 32: truncate the key material to that length; both CKA_VALUE and the CKA_VALUE_LEN attribute on the resulting object must reflect the truncated size.
  • CKA_VALUE_LEN present, value > 32: return CKR_WRAPPED_KEY_LEN_RANGE as 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

  1. changed the title [-]Strange `CKA_VALUE_LEN` handling in key encapsulation[/-] [+]Strange `CKA_VALUE_LEN` handling in key encapsulation (ML-KEM)[/+] on Jun 20, 2026
  2. bukka commented on Jun 21, 2026

    @bukka
    Member
  3. added theissue type on Jun 21, 2026
  4. antoinelochet commented on Jun 21, 2026

    @antoinelochet
    Contributor

    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.

  5. antoinelochet commented on Jun 21, 2026

    @antoinelochet
    Contributor

    @MatthiasValvekens You can try a fix with the branch here: #888

  6. MatthiasValvekens commented on Jun 21, 2026

    @MatthiasValvekens
    ContributorAuthor

    Thanks, that seems to work! My tests with key_length=256 and key_length=128 pass.

    There's another issue related to the encapsulation / decapsulation endpoint not handling pCipherText == NULL correctly (that indicates a probe request). I'll file a separate bug report for that one.

    EDIT: See #889.

  7. MatthiasValvekens commented on Jun 28, 2026

    @MatthiasValvekens
    ContributorAuthor

    Fixed in #888, so closing this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions