Skip to content

C_GetAttributeValue returns CKR_FUNCTION_FAILED for CKA_CHECK_VALUE on a secret key whose length is not an AES key length #245

Closed

Description

@pdb0102

Summary

wolfPKCS11 computes CKA_CHECK_VALUE for every CKO_SECRET_KEY object by encrypting a zero block with AES-ECB and taking the first three bytes of the result. That works only when the key is 16, 24, or 32 bytes long. For other lengths, AES key setup fails and C_GetAttributeValue returns CKR_FUNCTION_FAILED.

The failure also stops processing the attribute template. As a result, a request for CKA_CHECK_VALUE can prevent a consumer from reading any of the other attributes in that template.

For example, a 48-byte CKK_GENERIC_SECRET key (such as an HMAC-SHA384 key or a P-384 ECDH shared secret) and a 64-byte key (such as an HMAC-SHA512 key) cannot be read by a consumer that includes the common secret-key attribute CKA_CHECK_VALUE in its template. The length query succeeds and reports a length of 3, so the problem appears only when the consumer asks for the value.

I found this in wolfPKCS11 2.1.0 at commit caeaaa5693ad7b4253d6bc585e642381033a6a87 (2026-06-25). I also reproduced it on the head of pull request #243 (101fe0a, the stacked series #227 to #244), with #222 merged. Both builds were made from source on macOS arm64 against wolfSSL 5.9.2.

What the specification says

PKCS#11 Cryptoki Interface Base Specification Version 3.2, section 4.11 (Secret key objects), Table 30, lists CKA_CHECK_VALUE (“Key checksum”) as an attribute that secret-key objects may carry. A consumer reading a secret key may therefore include it in the requested attributes.

Section 6.8.2 (Generic secret key objects) defines the check value for this key type: "the first three bytes of the SHA-1 hash of the generic secret key object's CKA_VALUE attribute". Section 6.10.2 (AES secret key objects) defines the AES form, the ECB encryption of a block of null bytes. The HMAC key types define none, so for them the attribute is unavailable.

Section 5.7, C_GetAttributeValue, says:

Otherwise, if the specified value for the object is invalid (the object does not possess such an attribute), then the ulValueLen field in that triple is modified to hold the value CK_UNAVAILABLE_INFORMATION.

It also says:

Note that the error codes CKR_ATTRIBUTE_SENSITIVE, CKR_ATTRIBUTE_TYPE_INVALID, and CKR_BUFFER_TOO_SMALL do not denote true errors for C_GetAttributeValue. If a call to C_GetAttributeValue returns any of these three values, then the call MUST nonetheless have processed every attribute in the template supplied to C_GetAttributeValue.

CKR_FUNCTION_FAILED indicates a function failure; it is not the appropriate way to report that an attribute is unavailable or cannot be computed for a particular key type.

Steps to reproduce

Initialize a token with a user PIN, set WOLFPKCS11_TOKEN_PATH to its store, and run the program below against the module:

repro <path to libwolfpkcs11> <user pin>

The program creates session CKK_GENERIC_SECRET keys of different lengths with C_CreateObject, then reads CKA_CHECK_VALUE twice: first as a length query, then into a three-byte buffer.

#include <dlfcn.h>
#include <stdio.h>
#include <string.h>
#define CK_PTR *
#define CK_DECLARE_FUNCTION(returnType, name) returnType name
#define CK_DECLARE_FUNCTION_POINTER(returnType, name) returnType (* name)
#define CK_CALLBACK_FUNCTION(returnType, name) returnType (* name)
#define NULL_PTR 0
#include "pkcs11.h"

static CK_RV read_check_value(CK_FUNCTION_LIST_PTR fl, CK_SESSION_HANDLE s, CK_ULONG length)
{
    CK_OBJECT_CLASS cls = CKO_SECRET_KEY;
    CK_KEY_TYPE type = CKK_GENERIC_SECRET;
    CK_BBOOL yes = CK_TRUE, no = CK_FALSE;
    CK_BYTE value[64];
    CK_BYTE kcv[3];
    CK_OBJECT_HANDLE h;
    CK_RV rv;
    CK_ATTRIBUTE make[] = {
        { CKA_CLASS, &cls, sizeof(cls) }, { CKA_KEY_TYPE, &type, sizeof(type) },
        { CKA_TOKEN, &no, sizeof(no) }, { CKA_SIGN, &yes, sizeof(yes) }, { CKA_VERIFY, &yes, sizeof(yes) },
        { CKA_SENSITIVE, &no, sizeof(no) }, { CKA_EXTRACTABLE, &yes, sizeof(yes) },
        { CKA_VALUE, value, length },
    };
    CK_ATTRIBUTE query = { CKA_CHECK_VALUE, NULL, 0 };
    CK_ATTRIBUTE read = { CKA_CHECK_VALUE, kcv, sizeof(kcv) };

    memset(value, 0x5a, sizeof(value));
    rv = fl->C_CreateObject(s, make, 8, &h);
    if (rv != CKR_OK) {
        printf("%2lu bytes: C_CreateObject -> 0x%lx\n", (unsigned long)length, (unsigned long)rv);
        return rv;
    }
    rv = fl->C_GetAttributeValue(s, h, &query, 1);
    printf("%2lu bytes: length query -> 0x%lx, ulValueLen %ld\n", (unsigned long)length, (unsigned long)rv, (long)query.ulValueLen);
    rv = fl->C_GetAttributeValue(s, h, &read, 1);
    printf("%2lu bytes: value read   -> 0x%lx\n", (unsigned long)length, (unsigned long)rv);
    fl->C_DestroyObject(s, h);
    return rv;
}

int main(int argc, char** argv)
{
    void* lib;
    CK_RV (*get_list)(CK_FUNCTION_LIST_PTR_PTR);
    CK_FUNCTION_LIST_PTR fl;
    CK_SLOT_ID slot;
    CK_ULONG count = 1;
    CK_SESSION_HANDLE s;

    if (argc != 3) {
        fprintf(stderr, "usage: repro <module> <user pin>\n");
        return 2;
    }
    lib = dlopen(argv[1], RTLD_NOW);
    get_list = (CK_RV (*)(CK_FUNCTION_LIST_PTR_PTR))dlsym(lib, "C_GetFunctionList");
    get_list(&fl);
    fl->C_Initialize(NULL);
    fl->C_GetSlotList(CK_TRUE, &slot, &count);
    fl->C_OpenSession(slot, CKF_SERIAL_SESSION | CKF_RW_SESSION, NULL, NULL, &s);
    fl->C_Login(s, CKU_USER, (CK_UTF8CHAR_PTR)argv[2], strlen(argv[2]));
    read_check_value(fl, s, 32);
    read_check_value(fl, s, 48);
    read_check_value(fl, s, 64);
    fl->C_Logout(s);
    fl->C_CloseSession(s);
    fl->C_Finalize(NULL);
    return 0;
}

Output on wolfPKCS11 2.1.0 (macOS arm64, wolfSSL 5.9.2, --enable-aesecb); 0x6 is CKR_FUNCTION_FAILED:

32 bytes: length query -> 0x0, ulValueLen 3
32 bytes: value read   -> 0x0
48 bytes: length query -> 0x0, ulValueLen 3
48 bytes: value read   -> 0x6
64 bytes: length query -> 0x0, ulValueLen 3
64 bytes: value read   -> 0x6

The same result occurs on the head of pull request #243 with #222 merged.

A 32-byte CKK_AES key reads successfully, as expected. A 32-byte generic secret also reads successfully, but the value is wrong: it is the AES-256 check value, where section 6.8.2 defines the SHA-1 form.

The problem is not limited to callers requesting CKA_CHECK_VALUE by itself. If the template also includes attributes such as CKA_CLASS, CKA_KEY_TYPE, or CKA_VALUE_LEN, the call still returns CKR_FUNCTION_FAILED and leaves those attributes unfilled. A consumer using the common secret-key attributes therefore cannot read a 48- or 64-byte key, or enumerate a token containing one, if its template includes CKA_CHECK_VALUE.

Where it is in the source

In src/internal.c (wolfPKCS11 2.1.0 line numbers):

  • 11448–11459, WP11_Object_GetAttr, case CKA_CHECK_VALUE: For CKO_SECRET_KEY, the code calls GetEcbCheckValue(object, data, len) under HAVE_AESECB, regardless of the secret key's type.
  • 11233, GetEcbCheckValue: With a NULL buffer, it reports PKCS11_CHECK_VALUE_SIZE and returns. With a buffer, it encrypts a zero block using WP11_AesEcb_Encrypt(input, inLen, hash, &inLen, secret, secret->session), passing the secret-key object itself as the AES key. If that call fails, the function returns CKR_FUNCTION_FAILED. wolfCrypt's AES key setup rejects 48- and 64-byte keys.
  • src/crypto.c, C_GetAttributeValue: The final per-attribute result-mapping branch handles any other nonzero return with CKR_FUNCTION_FAILED and returns immediately, stopping processing of the template.

Suggested fix

Select the algorithm by key type: CKK_AES keeps the ECB check value; CKK_GENERIC_SECRET takes the first three bytes of SHA-1 over the key value, which GetSha1CheckValue in the same file already computes for certificates; the HMAC key types report NOT_AVAILABLE_E, which C_GetAttributeValue maps to CK_UNAVAILABLE_INFORMATION.

At a minimum, a failed check-value computation should be reported as an unavailable attribute rather than CKR_FUNCTION_FAILED, so the rest of the template can still be processed.

Why it matters

A PKCS#11 consumer that requests the common secret-key attributes cannot read HMAC-SHA384 or HMAC-SHA512 keys, or P-384 or P-521 ECDH shared secrets, if its template includes CKA_CHECK_VALUE. It can also fail to enumerate a token containing one of these keys.

The length query succeeds, so the failure is easy to miss until the consumer tries to read the value. The underlying issue is that an attribute request for a check value can prevent the caller from retrieving otherwise valid attributes from the same object.

Activity

  1. pdb0102 commented on Oct 10, 2026

    @pdb0102
    Author

    I'm not comfortable enough to provide a PR (not sure about the rules and style and commenting, and some projects are picky) but the following fix worked for me:

    diff --git a/src/internal.c b/src/internal.c
    index eb5fbd8..dfa5a7f 100644
    --- a/src/internal.c
    +++ b/src/internal.c
    @@ -14845,11 +14845,22 @@ int WP11_Object_GetAttr(WP11_Object* object, CK_ATTRIBUTE_TYPE type, byte* data,
     #endif
                 }
                 else if (object->objClass == CKO_SECRET_KEY) {
    +                switch (object->type) {
     #ifdef HAVE_AESECB
    -                ret = GetEcbCheckValue(object, data, len);
    -#else
    -                ret = NOT_AVAILABLE_E;
    +                    case CKK_AES:
    +                        ret = GetEcbCheckValue(object, data, len);
    +                        break;
     #endif
    +#ifndef NO_SHA
    +                    case CKK_GENERIC_SECRET:
    +                        ret = GetSha1CheckValue(object->data.symmKey->data,
    +                            object->data.symmKey->len, data, len);
    +                        break;
    +#endif
    +                    default:
    +                        ret = NOT_AVAILABLE_E;
    +                        break;
    +                }
                 }
                 else
                     ret = NOT_AVAILABLE_E;
    diff --git a/tests/ecb_check_value_error_test.c b/tests/ecb_check_value_error_test.c
    index e41ad1f..ecc8a6a 100644
    --- a/tests/ecb_check_value_error_test.c
    +++ b/tests/ecb_check_value_error_test.c
    @@ -18,12 +18,12 @@
      * along with this program; if not, write to the Free Software
      * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA
      *
    - * Test for GetEcbCheckValue error propagation (bug #496).
    + * Test for CKA_CHECK_VALUE on generic secret keys.
      *
    - * Verifies that GetEcbCheckValue correctly propagates failures from
    - * WP11_AesEcb_Encrypt. A generic secret key with an invalid AES key length
    - * (e.g. 5 bytes) causes the encrypt to fail, and C_GetAttributeValue must
    - * return CKR_FUNCTION_FAILED.
    + * Verifies that a CKK_GENERIC_SECRET key of any length returns a CKA_CHECK_VALUE
    + * with the first three bytes of the SHA-1 hash of CKA_VALUE (PKCS#11 3.2
    + * section 6.8.2). A key whose length is not an AES key length (e.g.
    + * 5, 48 or 64 bytes) can still be read.
      */
     
     #ifdef HAVE_CONFIG_H
    @@ -251,7 +251,7 @@ static int test_valid_key_check_value(CK_SESSION_HANDLE session)
         printf("PASS: Test1: check value length is 3\n");
         test_passed++;
     
    -    /* Verify not all zeros (encrypt of zero block should produce non-zero) */
    +    /* Verify not all zeros (the SHA-1 of the key should produce non-zero) */
         if (checkValue[0] == 0 && checkValue[1] == 0 && checkValue[2] == 0) {
             fprintf(stderr,
                     "FAIL: Test1: check value is all zeros (likely not computed)\n");
    @@ -269,11 +269,9 @@ cleanup:
     
     /*
      * Test 2: Create a 5-byte CKK_GENERIC_SECRET key and query CKA_CHECK_VALUE.
    - * The 5-byte key is not a valid AES key length, so WP11_AesEcb_Encrypt
    - * (called by GetEcbCheckValue) fails internally.
    - *
    - * Verifies the fix for BUG #496: GetEcbCheckValue now correctly propagates
    - * the encrypt failure, returning CKR_FUNCTION_FAILED.
    + * A generic secret key's check value is the first three bytes of the SHA-1
    + * hash of CKA_VALUE (PKCS#11 3.2 section 6.8.2); AES-ECB check values are only
    + * computed for CKK_AES keys.
      */
     static int test_invalid_key_check_value(CK_SESSION_HANDLE session)
     {
    @@ -304,14 +302,32 @@ static int test_invalid_key_check_value(CK_SESSION_HANDLE session)
         CHECK_CKR(ret, "Test2: C_CreateObject (5-byte generic secret)", CKR_OK);
     
         ret = funcList->C_GetAttributeValue(session, key, getTmpl, 1);
    -
    -    /*
    -     * BUG #496: GetEcbCheckValue returns CKR_OK even though
    -     * WP11_AesEcb_Encrypt failed (5 bytes is not a valid AES key length).
    -     * The correct behavior is to return CKR_FUNCTION_FAILED.
    -     */
         CHECK_CKR(ret, "Test2: C_GetAttributeValue CKA_CHECK_VALUE (5-byte key)",
    -              CKR_FUNCTION_FAILED);
    +              CKR_OK);
    +
    +    if (getTmpl[0].ulValueLen != 3) {
    +        fprintf(stderr,
    +                "FAIL: Test2: expected check value length 3, got %lu\n",
    +                (unsigned long)getTmpl[0].ulValueLen);
    +        test_failed++;
    +        result = -1;
    +        goto cleanup;
    +    }
    +    printf("PASS: Test2: check value length is 3\n");
    +    test_passed++;
    +
    +    /* SHA-1 of five 0xBB bytes, first three bytes. */
    +    if (checkValue[0] != 0x8B || checkValue[1] != 0x28 || checkValue[2] != 0x8C) {
    +        fprintf(stderr,
    +                "FAIL: Test2: check value %02x%02x%02x is not the SHA-1 value 8b288c\n",
    +                checkValue[0], checkValue[1], checkValue[2]);
    +        test_failed++;
    +        result = -1;
    +        goto cleanup;
    +    }
    +    printf("PASS: Test2: check value is the SHA-1 value %02x%02x%02x\n",
    +           checkValue[0], checkValue[1], checkValue[2]);
    +    test_passed++;
     
     cleanup:
         return result;
    @@ -393,7 +409,7 @@ static int ecb_check_value_error_test(void)
         if (test_valid_key_check_value(session) != 0)
             result = -1;
     
    -    /* Test 2: Invalid 5-byte key — demonstrates bug #496 */
    +    /* Test 2: 5-byte generic secret key, the SHA-1 check value */
         if (test_invalid_key_check_value(session) != 0)
             result = -1;
     
  2. LinuxJedi commented on Oct 12, 2026

    @LinuxJedi
    Member

    Many thanks for the report and the diff. You are welcome to contribute a PR for this, but we would need to get you to sign a CLA for it. If you are happy with that, please open a PR and the team will walk you through how to do it.

    Alternatively, I can work on a fix later this week.

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

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions