Open Bug 2075845 Opened 7 days ago Updated 3 days ago

C_VerifyInit returns CKR_MECHANISM_INVALID for CKM_AES_CMAC, although the mechanism advertises CKF_VERIFY and C_SignInit accepts it

Categories

(NSS :: Libraries, defect, P3)

Tracking

(Not tracked)

UNCONFIRMED

People

(Reporter: eric.knauel, Unassigned)

Details

Attachments

(2 files)

User Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/152.0.0.0 Safari/537.36

Steps to reproduce

softoken advertises sign and verify for both AES-CMAC mechanisms. Output of p11slotinfo (from https://github.com/Mastercard/pkcs11-tools), NSS 3.130, excerpt:

PKCS#11 Library
---------------
Name        : /opt/homebrew/Cellar/nss/3.130/lib/libsoftokn3.dylib
Lib version : 3.130
API version : 2.40
Description : NSS Internal Crypto Services
Manufacturer: Mozilla Foundation

Slot[1]
-------------
Slot Number : 2
Description : NSS User Private Key and Certificate Services
Manufacturer: Mozilla Foundation
Slot Flags  : [ CKF_TOKEN_PRESENT ]

Token
-------------
Label       : NSS Certificate DB
Manufacturer: Mozilla Foundation

Token Flags : [ CKF_RNG CKF_LOGIN_REQUIRED CKF_USER_PIN_INITIALIZED CKF_DUAL_CRYPTO_OPERATIONS CKF_TOKEN_INITIALIZED ]

Mechanisms:
-----------
[...]
CKM_AES_CMAC                              --- --- --- sig --- vfy --- --- --- --- --- --- --- --- SW (0000108a)
CKM_AES_CMAC_GENERAL                      --- --- --- sig --- vfy --- --- --- --- --- --- --- --- SW (0000108b)
[...]

Steps to reproduce: build and run the attached cmac_verify.c against softoken, with a fresh database created by certutil -N:

cc -I <dir containing the OASIS pkcs11.h> -o cmac_verify cmac_verify.c -ldl
NSS_LIB_PARAMS=configDir=<db dir> ./cmac_verify <path to libsoftokn3> <user PIN>

The program:

  1. generates a 128-bit AES session key with CKA_SIGN and CKA_VERIFY;
  2. reads the mechanism info for CKM_AES_CMAC;
  3. computes a MAC with C_SignInit/C_Sign;
  4. tries to verify it with C_VerifyInit/C_Verify, using the same key and mechanism and no parameter.

Program to reproduce:

/*
 * Minimal reproducer: C_VerifyInit rejects CKM_AES_CMAC, which C_SignInit accepts.
 *
 * Generates an AES key with CKA_SIGN and CKA_VERIFY, reads the mechanism info
 * for CKM_AES_CMAC, computes a MAC with C_SignInit/C_Sign and checks it with
 * C_VerifyInit/C_Verify -- same key, same mechanism, no parameter.
 *
 * Expected: C_GetMechanismInfo reports CKF_VERIFY for CKM_AES_CMAC, so
 * C_VerifyInit accepts it and C_Verify returns CKR_OK for the MAC just made.
 * PKCS#11 3.1 (6.14, Table 116 "Mechanisms vs. Functions") lists both AES-CMAC
 * mechanisms for sign and verify.
 *
 * Against NSS softoken 3.120 (Ubuntu 26.04's libnss3 2:3.120-1ubuntu2.1) the
 * mechanism info does advertise CKF_SIGN | CKF_VERIFY, and signing works, but
 * C_VerifyInit fails with CKR_MECHANISM_INVALID.
 *
 * In NSS's source (lib/softoken/pkcs11c.c) the mechanism switch in
 * NSC_SignInit has cases for CKM_AES_CMAC and CKM_AES_CMAC_GENERAL; the one in
 * NSC_VerifyInit has neither and falls through to its default,
 * CKR_MECHANISM_INVALID. CKM_AES_CMAC_GENERAL is therefore affected too; this
 * program only exercises CKM_AES_CMAC.
 *
 *   cc -I c_src -o /tmp/cmac_verify test/support/cmac_verify.c -ldl
 *   /tmp/cmac_verify <module.so> <user-pin> [token-label]
 *
 * NSS softoken finds its database through NSS_LIB_PARAMS and exposes two slots,
 * so the token is selected by label (default "NSS Certificate DB").
 * test/support/nss-spy-log.sh builds this against p11ex's test image and
 * writes cmac_verify-pkcs11-spy.log; see that script for how to run it.
 *
 * Exits non-zero if verification cannot be initialised or fails.
 */

#include <dlfcn.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define CK_PTR *
#define CK_DECLARE_FUNCTION(returnType, name) returnType name
#define CK_DECLARE_FUNCTION_POINTER(returnType, name) returnType(CK_PTR name)
#define CK_CALLBACK_FUNCTION(returnType, name) returnType(CK_PTR name)
#ifndef NULL_PTR
#define NULL_PTR NULL
#endif

#include "pkcs11.h"

static CK_FUNCTION_LIST_PTR p11;

#define CHECK(rv, what)                                                        \
  do {                                                                         \
    if ((rv) != CKR_OK) {                                                      \
      fprintf(stderr, "%s failed: 0x%lx\n", (what), (unsigned long)(rv));      \
      return 1;                                                                \
    }                                                                          \
  } while (0)

/* Token labels are blank-padded to 32 bytes. */
static int find_slot(const char *label, CK_SLOT_ID *slot) {
  CK_SLOT_ID slots[32];
  CK_ULONG count = 32;
  size_t n = strlen(label);

  if (p11->C_GetSlotList(CK_TRUE, slots, &count) != CKR_OK) {
    return 0;
  }
  for (CK_ULONG i = 0; i < count; i++) {
    CK_TOKEN_INFO info;
    int match = 1;
    if (p11->C_GetTokenInfo(slots[i], &info) != CKR_OK || n > sizeof(info.label) ||
        memcmp(info.label, label, n) != 0) {
      continue;
    }
    for (size_t j = n; j < sizeof(info.label); j++) {
      if (info.label[j] != ' ') {
        match = 0;
      }
    }
    if (match) {
      *slot = slots[i];
      return 1;
    }
  }
  return 0;
}

int main(int argc, char **argv) {

  void *handle;
  CK_C_GetFunctionList get_list;
  CK_SLOT_ID slot;
  const char *label;
  CK_SESSION_HANDLE session;
  CK_OBJECT_HANDLE key;
  CK_MECHANISM_INFO info;
  CK_RV rv;

  CK_MECHANISM mech = {CKM_AES_CMAC, NULL, 0};
  CK_BYTE msg[] = "p11ex AES-CMAC verify reproducer";
  CK_ULONG msg_len = (CK_ULONG)(sizeof(msg) - 1);
  CK_BYTE mac[64];
  CK_ULONG mac_len = sizeof(mac);

  if (argc < 3) {
    fprintf(stderr, "usage: %s <module.so> <user-pin> [token-label]\n", argv[0]);
    return 2;
  }
  label = argc > 3 ? argv[3] : "NSS Certificate DB";

  handle = dlopen(argv[1], RTLD_NOW);
  if (!handle) {
    fprintf(stderr, "dlopen: %s\n", dlerror());
    return 1;
  }
  get_list = (CK_C_GetFunctionList)dlsym(handle, "C_GetFunctionList");
  if (!get_list) {
    fprintf(stderr, "no C_GetFunctionList\n");
    return 1;
  }
  rv = get_list(&p11);
  CHECK(rv, "C_GetFunctionList");

  rv = p11->C_Initialize(NULL);
  CHECK(rv, "C_Initialize");
  if (!find_slot(label, &slot)) {
    fprintf(stderr, "no token labelled \"%s\"\n", label);
    return 1;
  }
  rv = p11->C_OpenSession(slot, CKF_SERIAL_SESSION | CKF_RW_SESSION, NULL, NULL,
                          &session);
  CHECK(rv, "C_OpenSession");
  rv = p11->C_Login(session, CKU_USER, (CK_UTF8CHAR_PTR)argv[2],
                    (CK_ULONG)strlen(argv[2]));
  CHECK(rv, "C_Login");

  {
    CK_MECHANISM keygen = {CKM_AES_KEY_GEN, NULL, 0};
    CK_BBOOL yes = CK_TRUE, no = CK_FALSE;
    CK_ULONG value_len = 16;
    CK_ATTRIBUTE tmpl[] = {{CKA_TOKEN, &no, sizeof(no)},
                           {CKA_VALUE_LEN, &value_len, sizeof(value_len)},
                           {CKA_SIGN, &yes, sizeof(yes)},
                           {CKA_VERIFY, &yes, sizeof(yes)}};
    rv = p11->C_GenerateKey(session, &keygen, tmpl, 4, &key);
    CHECK(rv, "C_GenerateKey");
  }

  printf("module: %s\n", argv[1]);
  printf("token:  %s\n\n", label);

  rv = p11->C_GetMechanismInfo(slot, CKM_AES_CMAC, &info);
  CHECK(rv, "C_GetMechanismInfo");
  printf("CKM_AES_CMAC mechanism info: flags 0x%lx (CKF_SIGN %s, CKF_VERIFY %s)\n\n",
         (unsigned long)info.flags, (info.flags & CKF_SIGN) ? "set" : "not set",
         (info.flags & CKF_VERIFY) ? "set" : "not set");

  rv = p11->C_SignInit(session, &mech, key);
  printf("C_SignInit(CKM_AES_CMAC)   -> rv 0x%lx\n", (unsigned long)rv);
  CHECK(rv, "C_SignInit");
  rv = p11->C_Sign(session, msg, msg_len, mac, &mac_len);
  printf("C_Sign                     -> rv 0x%lx, %lu byte MAC\n", (unsigned long)rv,
         (unsigned long)mac_len);
  CHECK(rv, "C_Sign");

  rv = p11->C_VerifyInit(session, &mech, key);
  printf("C_VerifyInit(CKM_AES_CMAC) -> rv 0x%lx%s\n", (unsigned long)rv,
         rv == CKR_MECHANISM_INVALID ? " CKR_MECHANISM_INVALID" : "");
  if (rv == CKR_OK) {
    rv = p11->C_Verify(session, msg, msg_len, mac, mac_len);
    printf("C_Verify                   -> rv 0x%lx\n", (unsigned long)rv);
  }

  p11->C_DestroyObject(session, key);
  p11->C_Logout(session);
  p11->C_CloseSession(session);
  p11->C_Finalize(NULL);

  if (rv != CKR_OK) {
    printf("\nFAIL: a MAC made with CKM_AES_CMAC cannot be verified with it\n");
    return 1;
  }
  printf("\nok: the MAC verifies\n");
  return 0;
}

Actual results

CKM_AES_CMAC mechanism info: flags 0x2800 (CKF_SIGN set, CKF_VERIFY set)

C_SignInit(CKM_AES_CMAC)   -> rv 0x0
C_Sign                     -> rv 0x0, 16 byte MAC
C_VerifyInit(CKM_AES_CMAC) -> rv 0x70 CKR_MECHANISM_INVALID

The attached pkcs11-spy log (NSS 3.120) shows the same sequence.

Expected results

C_VerifyInit succeeds and C_Verify returns CKR_OK for the MAC just produced:

  • The token's own mechanism info says CKF_VERIFY is supported (see above).
  • PKCS#11 3.1, section 6.14, Table 116 lists both CKM_AES_CMAC and CKM_AES_CMAC_GENERAL for Sign & Verify.
Attached file cmac_verify.c —
Severity: -- → S3
Priority: -- → P3
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Creator:
Created:
Updated:
Size: