diff --git a/README.md b/README.md index b81bc0c5..102b99e2 100644 --- a/README.md +++ b/README.md @@ -97,6 +97,52 @@ See wolfpkcs11/store.h for prototypes of functions to implement. Sets the private key's label against the public key when generating key pairs. +#### Define WOLFPKCS11_LEGACY_COPYABLE_FALSE_DEFAULT + +Restores the pre-fix behavior where `C_GetAttributeValue(CKA_COPYABLE)` always +reports `CK_FALSE`, regardless of whether the attribute was explicitly set. +The default (without this flag) reports `CK_TRUE` for new objects and reflects +the stored value for objects whose `CKA_COPYABLE` was explicitly set, matching +the PKCS#11 specification. Define this only if your application or stored +tokens depend on the old read-back behavior. The `C_CopyObject` enforcement +consults the stored copyable flag directly via `WP11_Object_IsCopyable`, so +this macro does not change which objects can be copied; objects created with +`CKA_COPYABLE=CK_TRUE` (the default) remain copyable, and objects created +with `CKA_COPYABLE=CK_FALSE` are still rejected by `C_CopyObject` with +`CKR_ACTION_PROHIBITED`. + +### Behavior changes for PKCS#11 spec compliance + +Several pre-existing gaps were closed; applications upgrading from earlier +versions may need to update templates or error-handling: + +- `C_DeriveKey` now enforces `CKA_DERIVE` on the base key. Keys that do not + set `CKA_DERIVE=CK_TRUE` at creation are rejected with + `CKR_KEY_TYPE_INCONSISTENT`. RSA, EC, and ML-DSA private keys default + `CKA_DERIVE` to `CK_FALSE`, so applications doing ECDH or other key + derivation must now set `CKA_DERIVE=CK_TRUE` explicitly in the key + template. The check is skipped on `--enable-nss` builds because NSS + generates ephemeral ECDHE keys without setting `CKA_DERIVE` and relies on + the historic permissive behavior; non-NSS builds get the spec-compliant + enforcement. +- `C_CopyObject` enforces `CKA_COPYABLE` (returns `CKR_ACTION_PROHIBITED` + on `CK_FALSE`). +- `C_DestroyObject` enforces `CKA_DESTROYABLE` (returns + `CKR_ACTION_PROHIBITED` on `CK_FALSE`). +- `C_EncapsulateKey`/`C_DecapsulateKey` enforce `CKA_ENCAPSULATE` / + `CKA_DECAPSULATE` and return `CKR_KEY_FUNCTION_NOT_PERMITTED` when the + flag is `CK_FALSE`. +- `C_SetAttributeValue` rejects flipping `CKA_COPYABLE` or + `CKA_DESTROYABLE` from `CK_FALSE` back to `CK_TRUE`, returning + `CKR_ATTRIBUTE_READ_ONLY` per spec section 4.4.1. +- `C_Login(CKU_SO, ...)` against a token whose SO PIN has not been set + now returns `CKR_USER_PIN_NOT_INITIALIZED` regardless of the PIN + length, closing an empty-PIN bypass where the zero-length constant + compare against the unset stored PIN returned equal. NSS builds + (`--enable-nss`) keep the empty-PIN probe accepted on uninitialized + tokens because `PK11_InitPin` bootstraps an empty-password NSS + database that way; non-empty PINs are still rejected. + #### Analog Devices, Inc. MAXQ10xx Secure Elements ([MAXQ1065](https://www.analog.com/en/products/maxq1065.html)/MAXQ1080) Support has been added to use the MAXQ10xx hardware for cryptographic operations diff --git a/src/crypto.c b/src/crypto.c index e338f38c..f1e972ac 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -387,7 +387,8 @@ static CK_RV CheckAttributes(CK_ATTRIBUTE* pTemplate, CK_ULONG ulCount, int set) return CKR_OK; } -static int CheckOpSupported(WP11_Object* obj, CK_ATTRIBUTE_TYPE op) +static int CheckOpSupportedRv(WP11_Object* obj, CK_ATTRIBUTE_TYPE op, + CK_RV errRv) { CK_BBOOL haveOp = 0; CK_ULONG dataLen = sizeof(haveOp); @@ -396,11 +397,16 @@ static int CheckOpSupported(WP11_Object* obj, CK_ATTRIBUTE_TYPE op) return ret; if (!haveOp) { WOLFPKCS11_MSG("Operation not supported by object"); - return CKR_KEY_TYPE_INCONSISTENT; + return (int)errRv; } return CKR_OK; } +static int CheckOpSupported(WP11_Object* obj, CK_ATTRIBUTE_TYPE op) +{ + return CheckOpSupportedRv(obj, op, CKR_KEY_TYPE_INCONSISTENT); +} + static CK_RV SetInitialStates(WP11_Object* key) { CK_RV rv; @@ -824,6 +830,26 @@ static CK_RV SetAttributeValue(WP11_Session* session, WP11_Object* obj, if ((getVar == CK_FALSE) && (*(CK_BBOOL*)attr->pValue == CK_TRUE)) return CKR_ATTRIBUTE_READ_ONLY; } + /* PKCS#11 v2.40 sec 4.4.1: once CKA_COPYABLE/CKA_DESTROYABLE has been + * set to CK_FALSE it cannot be set back to CK_TRUE. Read the stored + * flag bit directly so the check is independent of the GetAttr view + * (which the legacy macro may override). */ + if (!newObject && attr->type == CKA_COPYABLE) { + if (attr->pValue == NULL || + attr->ulValueLen != sizeof(CK_BBOOL)) + return CKR_ATTRIBUTE_VALUE_INVALID; + if (!WP11_Object_IsCopyable(obj) && + *(CK_BBOOL*)attr->pValue == CK_TRUE) + return CKR_ATTRIBUTE_READ_ONLY; + } + if (!newObject && attr->type == CKA_DESTROYABLE) { + if (attr->pValue == NULL || + attr->ulValueLen != sizeof(CK_BBOOL)) + return CKR_ATTRIBUTE_VALUE_INVALID; + if (!WP11_Object_IsDestroyable(obj) && + *(CK_BBOOL*)attr->pValue == CK_TRUE) + return CKR_ATTRIBUTE_READ_ONLY; + } ret = WP11_Object_SetAttr(obj, attr->type, (byte*)attr->pValue, attr->ulValueLen); if (ret == MEMORY_E) @@ -1337,6 +1363,17 @@ CK_RV C_CopyObject(CK_SESSION_HANDLE hSession, CK_OBJECT_HANDLE hObject, return rv; } } + + /* Reject copy of objects whose CKA_COPYABLE has been explicitly set to + * CK_FALSE. Read the underlying flag bit directly so this gate is not + * affected by WOLFPKCS11_LEGACY_COPYABLE_FALSE_DEFAULT (which only + * changes the C_GetAttributeValue view). */ + if (!WP11_Object_IsCopyable(obj)) { + rv = CKR_ACTION_PROHIBITED; + WOLFPKCS11_LEAVE("C_CopyObject", rv); + return rv; + } + keyType = WP11_Object_GetType(obj); FindAttributeType(pTemplate, ulCount, CKA_TOKEN, &attr); @@ -1449,6 +1486,15 @@ CK_RV C_DestroyObject(CK_SESSION_HANDLE hSession, return rv; } + /* Reject destruction of objects whose CKA_DESTROYABLE has been set to + * CK_FALSE. Read the flag bit directly so the gate stays consistent + * regardless of which getter view applies. */ + if (!WP11_Object_IsDestroyable(obj)) { + rv = CKR_ACTION_PROHIBITED; + WOLFPKCS11_LEAVE("C_DestroyObject", rv); + return rv; + } + rv = WP11_Session_RemoveObject(session, obj); WP11_Object_Free(obj); @@ -2313,8 +2359,10 @@ CK_RV C_Encrypt(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData, case CKM_AES_CBC: if (!WP11_Session_IsOpInitialized(session, WP11_INIT_AES_CBC_ENC)) return CKR_OPERATION_NOT_INITIALIZED; - if (!CK_ULONG_FITS_WORD32(ulDataLen)) + if (!CK_ULONG_FITS_WORD32(ulDataLen)) { + WP11_Session_SetOpInitialized(session, 0); return CKR_DATA_LEN_RANGE; + } encDataLen = (word32)ulDataLen; if (pEncryptedData == NULL) { @@ -2335,11 +2383,15 @@ CK_RV C_Encrypt(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData, WP11_INIT_AES_CBC_PAD_ENC)) { return CKR_OPERATION_NOT_INITIALIZED; } - if (!CK_ULONG_FITS_WORD32(ulDataLen)) + if (!CK_ULONG_FITS_WORD32(ulDataLen)) { + WP11_Session_SetOpInitialized(session, 0); return CKR_DATA_LEN_RANGE; + } /* Ensure padded result fits in word32 */ - if (ulDataLen > (CK_ULONG)(0xFFFFFFFF - AES_BLOCK_SIZE)) + if (ulDataLen > (CK_ULONG)(0xFFFFFFFF - AES_BLOCK_SIZE)) { + WP11_Session_SetOpInitialized(session, 0); return CKR_DATA_LEN_RANGE; + } /* PKCS#7 padding always adds at least 1 byte */ encDataLen = (word32)((ulDataLen / AES_BLOCK_SIZE) + 1) * @@ -2382,8 +2434,10 @@ CK_RV C_Encrypt(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData, case CKM_AES_GCM: if (!WP11_Session_IsOpInitialized(session, WP11_INIT_AES_GCM_ENC)) return CKR_OPERATION_NOT_INITIALIZED; - if (!CK_ULONG_FITS_WORD32(ulDataLen)) + if (!CK_ULONG_FITS_WORD32(ulDataLen)) { + WP11_Session_SetOpInitialized(session, 0); return CKR_DATA_LEN_RANGE; + } encDataLen = (word32)ulDataLen + WP11_AesGcm_GetTagBits(session) / 8; @@ -2405,8 +2459,10 @@ CK_RV C_Encrypt(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData, case CKM_AES_CCM: if (!WP11_Session_IsOpInitialized(session, WP11_INIT_AES_CCM_ENC)) return CKR_OPERATION_NOT_INITIALIZED; - if (!CK_ULONG_FITS_WORD32(ulDataLen)) + if (!CK_ULONG_FITS_WORD32(ulDataLen)) { + WP11_Session_SetOpInitialized(session, 0); return CKR_DATA_LEN_RANGE; + } encDataLen = (word32)ulDataLen + WP11_AesCcm_GetMacLen(session); @@ -2428,8 +2484,10 @@ CK_RV C_Encrypt(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData, case CKM_AES_ECB: if (!WP11_Session_IsOpInitialized(session, WP11_INIT_AES_ECB_ENC)) return CKR_OPERATION_NOT_INITIALIZED; - if (!CK_ULONG_FITS_WORD32(ulDataLen)) + if (!CK_ULONG_FITS_WORD32(ulDataLen)) { + WP11_Session_SetOpInitialized(session, 0); return CKR_DATA_LEN_RANGE; + } encDataLen = (word32)ulDataLen; if (pEncryptedData == NULL) { @@ -2470,8 +2528,10 @@ CK_RV C_Encrypt(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData, case CKM_AES_KEY_WRAP: if (!WP11_Session_IsOpInitialized(session, WP11_INIT_AES_KEYWRAP_ENC)) return CKR_OPERATION_NOT_INITIALIZED; - if (!CK_ULONG_FITS_WORD32(ulDataLen)) + if (!CK_ULONG_FITS_WORD32(ulDataLen)) { + WP11_Session_SetOpInitialized(session, 0); return CKR_DATA_LEN_RANGE; + } /* AES Key Wrap adds 8 bytes for the integrity check value */ encDataLen = (word32)(ulDataLen + KEYWRAP_BLOCK_SIZE); @@ -2608,8 +2668,10 @@ CK_RV C_EncryptUpdate(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pPart, WOLFPKCS11_LEAVE("C_EncryptUpdate", rv); return rv; } - if (!CK_ULONG_FITS_WORD32(ulPartLen)) + if (!CK_ULONG_FITS_WORD32(ulPartLen)) { + WP11_Session_SetOpInitialized(session, 0); return CKR_DATA_LEN_RANGE; + } encPartLen = (word32)ulPartLen + WP11_AesCbc_PartLen(session); encPartLen &= ~0xf; @@ -2629,6 +2691,7 @@ CK_RV C_EncryptUpdate(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pPart, pEncryptedPart, &encPartLen, session); if (ret < 0) { + WP11_Session_SetOpInitialized(session, 0); rv = CKR_FUNCTION_FAILED; WOLFPKCS11_LEAVE("C_EncryptUpdate", rv); return rv; @@ -2640,8 +2703,10 @@ CK_RV C_EncryptUpdate(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pPart, WP11_INIT_AES_CBC_PAD_ENC)) { return CKR_OPERATION_NOT_INITIALIZED; } - if (!CK_ULONG_FITS_WORD32(ulPartLen)) + if (!CK_ULONG_FITS_WORD32(ulPartLen)) { + WP11_Session_SetOpInitialized(session, 0); return CKR_DATA_LEN_RANGE; + } encPartLen = (word32)ulPartLen + WP11_AesCbc_PartLen(session); encPartLen &= ~0xf; @@ -2654,8 +2719,10 @@ CK_RV C_EncryptUpdate(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pPart, ret = WP11_AesCbcPad_EncryptUpdate(pPart, (int)ulPartLen, pEncryptedPart, &encPartLen, session); - if (ret < 0) + if (ret < 0) { + WP11_Session_SetOpInitialized(session, 0); return CKR_FUNCTION_FAILED; + } *pulEncryptedPartLen = encPartLen; break; #endif @@ -2674,8 +2741,10 @@ CK_RV C_EncryptUpdate(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pPart, encPartLen = (word32)*pulEncryptedPartLen; ret = WP11_AesCtr_Update(pPart, (int)ulPartLen, pEncryptedPart, &encPartLen, session); - if (ret < 0) + if (ret < 0) { + WP11_Session_SetOpInitialized(session, 0); return CKR_FUNCTION_FAILED; + } *pulEncryptedPartLen = encPartLen; break; #endif @@ -2695,8 +2764,10 @@ CK_RV C_EncryptUpdate(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pPart, ret = WP11_AesGcm_EncryptUpdate(pPart, (int)ulPartLen, pEncryptedPart, &encPartLen, obj, session); - if (ret < 0) + if (ret < 0) { + WP11_Session_SetOpInitialized(session, 0); return CKR_FUNCTION_FAILED; + } *pulEncryptedPartLen = encPartLen; break; #endif @@ -2715,8 +2786,10 @@ CK_RV C_EncryptUpdate(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pPart, pEncryptedPart, &encPartLen, session); if (ret == BUFFER_E) return CKR_BUFFER_TOO_SMALL; - if (ret < 0) + if (ret < 0) { + WP11_Session_SetOpInitialized(session, 0); return CKR_FUNCTION_FAILED; + } *pulEncryptedPartLen = encPartLen; break; #endif @@ -2726,6 +2799,7 @@ CK_RV C_EncryptUpdate(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pPart, (void)ret; (void)ulPartLen; (void)pEncryptedPart; + WP11_Session_SetOpInitialized(session, 0); rv = CKR_MECHANISM_INVALID; WOLFPKCS11_LEAVE("C_EncryptUpdate", rv); return rv; @@ -3319,8 +3393,10 @@ CK_RV C_Decrypt(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pEncryptedData, case CKM_AES_CBC: if (!WP11_Session_IsOpInitialized(session, WP11_INIT_AES_CBC_DEC)) return CKR_OPERATION_NOT_INITIALIZED; - if (!CK_ULONG_FITS_WORD32(ulEncryptedDataLen)) + if (!CK_ULONG_FITS_WORD32(ulEncryptedDataLen)) { + WP11_Session_SetOpInitialized(session, 0); return CKR_DATA_LEN_RANGE; + } decDataLen = (word32)ulEncryptedDataLen; if (pData == NULL) { @@ -3341,8 +3417,10 @@ CK_RV C_Decrypt(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pEncryptedData, WP11_INIT_AES_CBC_PAD_DEC)) { return CKR_OPERATION_NOT_INITIALIZED; } - if (!CK_ULONG_FITS_WORD32(ulEncryptedDataLen)) + if (!CK_ULONG_FITS_WORD32(ulEncryptedDataLen)) { + WP11_Session_SetOpInitialized(session, 0); return CKR_DATA_LEN_RANGE; + } decDataLen = (word32)ulEncryptedDataLen; if (pData == NULL) { @@ -3383,8 +3461,10 @@ CK_RV C_Decrypt(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pEncryptedData, if (!WP11_Session_IsOpInitialized(session, WP11_INIT_AES_GCM_DEC)) return CKR_OPERATION_NOT_INITIALIZED; - if (ulEncryptedDataLen < (CK_ULONG)WP11_AesGcm_GetTagBits(session) / 8) + if (ulEncryptedDataLen < (CK_ULONG)WP11_AesGcm_GetTagBits(session) / 8) { + WP11_Session_SetOpInitialized(session, 0); return CKR_ENCRYPTED_DATA_LEN_RANGE; + } decDataLen = (word32)ulEncryptedDataLen - WP11_AesGcm_GetTagBits(session) / 8; if (pData == NULL) { @@ -3406,8 +3486,10 @@ CK_RV C_Decrypt(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pEncryptedData, if (!WP11_Session_IsOpInitialized(session, WP11_INIT_AES_CCM_DEC)) return CKR_OPERATION_NOT_INITIALIZED; - if (ulEncryptedDataLen < (CK_ULONG)WP11_AesCcm_GetMacLen(session)) + if (ulEncryptedDataLen < (CK_ULONG)WP11_AesCcm_GetMacLen(session)) { + WP11_Session_SetOpInitialized(session, 0); return CKR_ENCRYPTED_DATA_LEN_RANGE; + } decDataLen = (word32)ulEncryptedDataLen - WP11_AesCcm_GetMacLen(session); if (pData == NULL) { @@ -3428,8 +3510,10 @@ CK_RV C_Decrypt(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pEncryptedData, case CKM_AES_ECB: if (!WP11_Session_IsOpInitialized(session, WP11_INIT_AES_ECB_DEC)) return CKR_OPERATION_NOT_INITIALIZED; - if (!CK_ULONG_FITS_WORD32(ulEncryptedDataLen)) + if (!CK_ULONG_FITS_WORD32(ulEncryptedDataLen)) { + WP11_Session_SetOpInitialized(session, 0); return CKR_DATA_LEN_RANGE; + } decDataLen = (word32)ulEncryptedDataLen; if (pData == NULL) { @@ -3476,8 +3560,10 @@ CK_RV C_Decrypt(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pEncryptedData, /* AES Key Wrap ciphertext is at least two semiblocks: one data * semiblock plus the 8-byte integrity check value. */ - if (ulEncryptedDataLen < 2 * KEYWRAP_BLOCK_SIZE) + if (ulEncryptedDataLen < 2 * KEYWRAP_BLOCK_SIZE) { + WP11_Session_SetOpInitialized(session, 0); return CKR_ENCRYPTED_DATA_LEN_RANGE; + } decDataLen = (word32)(ulEncryptedDataLen - KEYWRAP_BLOCK_SIZE); if (pData == NULL) { *pulDataLen = decDataLen; @@ -3603,8 +3689,10 @@ CK_RV C_DecryptUpdate(CK_SESSION_HANDLE hSession, case CKM_AES_CBC: if (!WP11_Session_IsOpInitialized(session, WP11_INIT_AES_CBC_DEC)) return CKR_OPERATION_NOT_INITIALIZED; - if (!CK_ULONG_FITS_WORD32(ulEncryptedPartLen)) + if (!CK_ULONG_FITS_WORD32(ulEncryptedPartLen)) { + WP11_Session_SetOpInitialized(session, 0); return CKR_DATA_LEN_RANGE; + } decPartLen = (word32)ulEncryptedPartLen + WP11_AesCbc_PartLen(session); @@ -3619,8 +3707,10 @@ CK_RV C_DecryptUpdate(CK_SESSION_HANDLE hSession, ret = WP11_AesCbc_DecryptUpdate(pEncryptedPart, (int)ulEncryptedPartLen, pPart, &decPartLen, session); - if (ret < 0) + if (ret < 0) { + WP11_Session_SetOpInitialized(session, 0); return CKR_FUNCTION_FAILED; + } *pulPartLen = decPartLen; break; case CKM_AES_CBC_PAD: @@ -3628,8 +3718,10 @@ CK_RV C_DecryptUpdate(CK_SESSION_HANDLE hSession, WP11_INIT_AES_CBC_PAD_DEC)) { return CKR_OPERATION_NOT_INITIALIZED; } - if (!CK_ULONG_FITS_WORD32(ulEncryptedPartLen)) + if (!CK_ULONG_FITS_WORD32(ulEncryptedPartLen)) { + WP11_Session_SetOpInitialized(session, 0); return CKR_DATA_LEN_RANGE; + } decPartLen = (word32)ulEncryptedPartLen + WP11_AesCbc_PartLen(session); @@ -3648,8 +3740,10 @@ CK_RV C_DecryptUpdate(CK_SESSION_HANDLE hSession, ret = WP11_AesCbcPad_DecryptUpdate(pEncryptedPart, (int)ulEncryptedPartLen, pPart, &decPartLen, session); - if (ret < 0) + if (ret < 0) { + WP11_Session_SetOpInitialized(session, 0); return CKR_FUNCTION_FAILED; + } *pulPartLen = decPartLen; break; #endif @@ -3668,8 +3762,10 @@ CK_RV C_DecryptUpdate(CK_SESSION_HANDLE hSession, decPartLen = (word32)*pulPartLen; ret = WP11_AesCtr_Update(pEncryptedPart, (word32)ulEncryptedPartLen, pPart, &decPartLen, session); - if (ret < 0) + if (ret < 0) { + WP11_Session_SetOpInitialized(session, 0); return CKR_FUNCTION_FAILED; + } *pulPartLen = decPartLen; break; #endif @@ -3684,8 +3780,10 @@ CK_RV C_DecryptUpdate(CK_SESSION_HANDLE hSession, ret = WP11_AesGcm_DecryptUpdate(pEncryptedPart, (int)ulEncryptedPartLen, session); - if (ret < 0) + if (ret < 0) { + WP11_Session_SetOpInitialized(session, 0); return CKR_FUNCTION_FAILED; + } break; #endif #ifdef HAVE_AESCTS @@ -3703,8 +3801,10 @@ CK_RV C_DecryptUpdate(CK_SESSION_HANDLE hSession, (word32)ulEncryptedPartLen, pPart, &decPartLen, session); if (ret == BUFFER_E) return CKR_BUFFER_TOO_SMALL; - if (ret < 0) + if (ret < 0) { + WP11_Session_SetOpInitialized(session, 0); return CKR_FUNCTION_FAILED; + } *pulPartLen = decPartLen; break; #endif @@ -3714,6 +3814,7 @@ CK_RV C_DecryptUpdate(CK_SESSION_HANDLE hSession, (void)ret; (void)ulEncryptedPartLen; (void)pPart; + WP11_Session_SetOpInitialized(session, 0); return CKR_MECHANISM_INVALID; } @@ -4066,8 +4167,10 @@ CK_RV C_DigestUpdate(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pPart, ret = WP11_Digest_Update(pPart, (word32)ulPartLen, session); - if (ret < 0) + if (ret < 0) { + WP11_Session_SetOpInitialized(session, 0); return CKR_FUNCTION_FAILED; + } return CKR_OK; } @@ -4104,17 +4207,28 @@ CK_RV C_DigestKey(CK_SESSION_HANDLE hSession, CK_OBJECT_HANDLE hKey) } if (WP11_Session_Get(hSession, &session) != 0) return CKR_SESSION_HANDLE_INVALID; + if (!WP11_Session_IsOpInitialized(session, WP11_INIT_DIGEST)) + return CKR_OPERATION_NOT_INITIALIZED; ret = WP11_Object_Find(session, hKey, &obj); - if (ret != 0) + if (ret != 0) { + WP11_Session_SetOpInitialized(session, 0); return CKR_OBJECT_HANDLE_INVALID; + } ret = WP11_Digest_Key(obj, session); - if (ret < 0) + if (ret < 0) { + WP11_Session_SetOpInitialized(session, 0); return CKR_FUNCTION_FAILED; - if (ret > 0) + } + if (ret > 0) { + /* Positive return is a CK_RV (e.g. CKR_FUNCTION_NOT_SUPPORTED on + * WOLFPKCS11_NO_STORE builds). Pass it through but still terminate + * the digest operation so the session is not left active. */ + WP11_Session_SetOpInitialized(session, 0); return (CK_RV)ret; + } return CKR_OK; } @@ -5129,10 +5243,13 @@ CK_RV C_SignUpdate(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pPart, #endif default: (void)ulPartLen; + WP11_Session_SetOpInitialized(session, 0); return CKR_MECHANISM_INVALID; } - if (ret < 0) + if (ret < 0) { + WP11_Session_SetOpInitialized(session, 0); return CKR_FUNCTION_FAILED; + } return CKR_OK; } @@ -6146,10 +6263,13 @@ CK_RV C_VerifyUpdate(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pPart, #endif default: (void)ulPartLen; + WP11_Session_SetOpInitialized(session, 0); return CKR_MECHANISM_INVALID; } - if (ret < 0) + if (ret < 0) { + WP11_Session_SetOpInitialized(session, 0); return CKR_FUNCTION_FAILED; + } return CKR_OK; } @@ -8193,6 +8313,16 @@ CK_RV C_DeriveKey(CK_SESSION_HANDLE hSession, if (ret != 0) return CKR_OBJECT_HANDLE_INVALID; +#ifndef WOLFPKCS11_NSS + /* Spec-compliance gate: reject keys whose CKA_DERIVE is CK_FALSE. NSS + * generates ephemeral EC keys for TLS ECDHE without explicitly setting + * CKA_DERIVE=CK_TRUE and relies on the historic permissive behavior, so + * skip this check in NSS builds. */ + ret = CheckOpSupported(obj, CKA_DERIVE); + if (ret != CKR_OK) + return ret; +#endif + switch (pMechanism->mechanism) { #ifdef HAVE_ECC case CKM_ECDH1_DERIVE: { @@ -9006,6 +9136,11 @@ CK_RV C_EncapsulateKey(CK_SESSION_HANDLE hSession, CK_MECHANISM_PTR pMechanism, if (WP11_Object_GetClass(pubObj) != CKO_PUBLIC_KEY) return CKR_KEY_HANDLE_INVALID; + ret = CheckOpSupportedRv(pubObj, CKA_ENCAPSULATE, + CKR_KEY_FUNCTION_NOT_PERMITTED); + if (ret != CKR_OK) + return (CK_RV)ret; + /* Only require R/W session for token objects */ if (!WP11_Session_IsRW(session)) { CK_ATTRIBUTE* tokenAttr = NULL; @@ -9118,6 +9253,11 @@ CK_RV C_DecapsulateKey(CK_SESSION_HANDLE hSession, CK_MECHANISM_PTR pMechanism, if (WP11_Object_GetClass(privObj) != CKO_PRIVATE_KEY) return CKR_KEY_HANDLE_INVALID; + ret = CheckOpSupportedRv(privObj, CKA_DECAPSULATE, + CKR_KEY_FUNCTION_NOT_PERMITTED); + if (ret != CKR_OK) + return (CK_RV)ret; + /* Only require R/W session for token objects */ if (!WP11_Session_IsRW(session)) { CK_ATTRIBUTE* tokenAttr = NULL; diff --git a/src/internal.c b/src/internal.c index e626dfd3..4900d402 100644 --- a/src/internal.c +++ b/src/internal.c @@ -7012,11 +7012,20 @@ int WP11_Slot_CheckSOPin(WP11_Slot* slot, char* pin, int pinLen) if (token->state != WP11_TOKEN_STATE_INITIALIZED) ret = PIN_NOT_SET_E; - /* NSS PK11_InitPin tries to login with an empty pin before setting the pin. - * This is effectively a public access, so should be OK. + /* When the SO PIN has not been set, reject any PIN check; otherwise an + * empty PIN would constant-compare equal to the unset zero-length + * stored PIN and grant SO authentication. NSS's PK11_InitPin bootstraps + * a fresh database by calling C_Login(CKU_SO, "", 0) before any SO PIN + * exists and relies on that probe succeeding, so for NSS builds the + * empty-PIN path is left intact and only non-empty PINs are rejected. */ +#ifdef WOLFPKCS11_NSS if (!(token->tokenFlags & WP11_TOKEN_FLAG_SO_PIN_SET) && pinLen > 0) ret = PIN_NOT_SET_E; +#else + if (!(token->tokenFlags & WP11_TOKEN_FLAG_SO_PIN_SET)) + ret = PIN_NOT_SET_E; +#endif if (ret == 0) { WP11_Lock_UnlockRO(&slot->lock); @@ -8941,6 +8950,32 @@ CK_OBJECT_CLASS WP11_Object_GetClass(WP11_Object* object) return object->objClass; } +/** + * Check whether the object is copyable. + * + * Reads the underlying WP11_FLAG_NOT_COPYABLE bit directly so the result is + * not affected by the WOLFPKCS11_LEGACY_COPYABLE_FALSE_DEFAULT macro that + * controls the C_GetAttributeValue view. + * + * @param object [in] Object object. + * @return 1 when copyable, 0 when not. + */ +int WP11_Object_IsCopyable(WP11_Object* object) +{ + return (object->opFlag & WP11_FLAG_NOT_COPYABLE) == 0; +} + +/** + * Check whether the object is destroyable. + * + * @param object [in] Object object. + * @return 1 when destroyable, 0 when not. + */ +int WP11_Object_IsDestroyable(WP11_Object* object) +{ + return (object->opFlag & WP11_FLAG_NOT_DESTROYABLE) == 0; +} + #if !defined(NO_RSA) || defined(HAVE_ECC) /** * Set the multi-precision integer from the data. @@ -10866,10 +10901,16 @@ int WP11_Object_GetAttr(WP11_Object* object, CK_ATTRIBUTE_TYPE type, byte* data, ret = GetOpFlagBool(object->opFlag, WP11_FLAG_TRUSTED, data, len); break; case CKA_COPYABLE: +#ifdef WOLFPKCS11_LEGACY_COPYABLE_FALSE_DEFAULT ret = GetBool(CK_FALSE, data, len); +#else + ret = GetBool( + !(object->opFlag & WP11_FLAG_NOT_COPYABLE), data, len); +#endif break; case CKA_DESTROYABLE: - ret = GetBool(CK_TRUE, data, len); + ret = GetBool( + !(object->opFlag & WP11_FLAG_NOT_DESTROYABLE), data, len); break; case CKA_APPLICATION: if (object->objClass == CKO_DATA) { @@ -11220,6 +11261,15 @@ int WP11_Object_SetAttr(WP11_Object* object, CK_ATTRIBUTE_TYPE type, byte* data, case CKA_DERIVE: WP11_Object_SetOpFlag(object, WP11_FLAG_DERIVE, *(CK_BBOOL*)data); break; + case CKA_COPYABLE: + /* Stored as the inverse: flag set when value is CK_FALSE. */ + WP11_Object_SetOpFlag(object, WP11_FLAG_NOT_COPYABLE, + !*(CK_BBOOL*)data); + break; + case CKA_DESTROYABLE: + WP11_Object_SetOpFlag(object, WP11_FLAG_NOT_DESTROYABLE, + !*(CK_BBOOL*)data); + break; case CKA_ID: ret = WP11_Object_SetKeyId(object, data, (int)len); break; diff --git a/tests/include.am b/tests/include.am index c22c5741..6943679e 100644 --- a/tests/include.am +++ b/tests/include.am @@ -41,6 +41,11 @@ noinst_PROGRAMS += tests/empty_pin_store_test tests_empty_pin_store_test_SOURCES = tests/empty_pin_store_test.c tests_empty_pin_store_test_LDADD = +check_PROGRAMS += tests/so_login_uninit_test +noinst_PROGRAMS += tests/so_login_uninit_test +tests_so_login_uninit_test_SOURCES = tests/so_login_uninit_test.c +tests_so_login_uninit_test_LDADD = + check_PROGRAMS += tests/find_objects_null_template_test noinst_PROGRAMS += tests/find_objects_null_template_test tests_find_objects_null_template_test_SOURCES = tests/find_objects_null_template_test.c @@ -90,6 +95,7 @@ tests_rsa_session_persistence_test_LDADD += src/libwolfpkcs11.la tests_debug_test_LDADD += src/libwolfpkcs11.la tests_object_id_uniqueness_test_LDADD += src/libwolfpkcs11.la tests_empty_pin_store_test_LDADD += src/libwolfpkcs11.la +tests_so_login_uninit_test_LDADD += src/libwolfpkcs11.la tests_find_objects_null_template_test_LDADD += src/libwolfpkcs11.la tests_aes_cbc_pad_padding_test_LDADD += src/libwolfpkcs11.la tests_ecb_check_value_error_test_LDADD += src/libwolfpkcs11.la @@ -101,6 +107,7 @@ tests_rsa_exponent_test_LDADD += src/libwolfpkcs11.la else tests_object_id_uniqueness_test_LDADD += src/libwolfpkcs11.la tests_empty_pin_store_test_LDADD += src/libwolfpkcs11.la +tests_so_login_uninit_test_LDADD += src/libwolfpkcs11.la tests_find_objects_null_template_test_LDADD += src/libwolfpkcs11.la tests_aes_cbc_pad_padding_test_LDADD += src/libwolfpkcs11.la tests_ecb_check_value_error_test_LDADD += src/libwolfpkcs11.la diff --git a/tests/pkcs11mtt.c b/tests/pkcs11mtt.c index 31dbadf2..9243139b 100644 --- a/tests/pkcs11mtt.c +++ b/tests/pkcs11mtt.c @@ -805,6 +805,7 @@ static CK_RV get_generic_key(CK_SESSION_HANDLE session, unsigned char* data, { CKA_SENSITIVE, &sensitive, sizeof(CK_BBOOL) }, { CKA_SIGN, &ckTrue, sizeof(ckTrue) }, { CKA_VERIFY, &ckTrue, sizeof(ckTrue) }, + { CKA_DERIVE, &ckTrue, sizeof(ckTrue) }, { CKA_VALUE, data, len }, }; int cnt = sizeof(generic_key)/sizeof(*generic_key); @@ -1258,6 +1259,18 @@ static CK_RV test_digest(void* args) CHECK_CKR_FAIL(ret, CKR_SESSION_HANDLE_INVALID, "Digest Key invalid session handle"); } + if (ret == CKR_OK) { + /* C_DigestKey must return CKR_OPERATION_NOT_INITIALIZED before any + * other validation when C_DigestInit has not been called. */ + ret = funcList->C_DigestKey(session, key); + CHECK_CKR_FAIL(ret, CKR_OPERATION_NOT_INITIALIZED, + "Digest Key without DigestInit"); + } + if (ret == CKR_OK) { + /* Now initialize and exercise the invalid-object-handle path. */ + ret = funcList->C_DigestInit(session, &mech); + CHECK_CKR(ret, "Digest Init for invalid-handle case"); + } if (ret == CKR_OK) { ret = funcList->C_DigestKey(session, CK_INVALID_HANDLE); CHECK_CKR_FAIL(ret, CKR_OBJECT_HANDLE_INVALID, @@ -3434,6 +3447,7 @@ static CK_OBJECT_HANDLE get_ecc_priv_key(CK_SESSION_HANDLE session, { CKA_EXTRACTABLE, &extractable, sizeof(CK_BBOOL) }, { CKA_SENSITIVE, &sensitive, sizeof(CK_BBOOL) }, { CKA_VERIFY, &ckTrue, sizeof(ckTrue) }, + { CKA_DERIVE, &ckTrue, sizeof(ckTrue) }, { CKA_EC_PARAMS, ecc_p256_params, sizeof(ecc_p256_params) }, { CKA_VALUE, ecc_p256_priv, sizeof(ecc_p256_priv) }, }; diff --git a/tests/pkcs11test.c b/tests/pkcs11test.c index 2faff094..0bc23a1a 100644 --- a/tests/pkcs11test.c +++ b/tests/pkcs11test.c @@ -4308,6 +4308,7 @@ static CK_RV get_generic_key(CK_SESSION_HANDLE session, unsigned char* data, { CKA_EXTRACTABLE, &extractable, sizeof(CK_BBOOL) }, { CKA_SIGN, &ckTrue, sizeof(ckTrue) }, { CKA_VERIFY, &ckTrue, sizeof(ckTrue) }, + { CKA_DERIVE, &ckTrue, sizeof(ckTrue) }, { CKA_VALUE, data, len }, }; int cnt = sizeof(generic_key)/sizeof(*generic_key); @@ -4640,6 +4641,7 @@ static CK_RV get_aes_128_key(CK_SESSION_HANDLE session, unsigned char* id, { CKA_DECRYPT, &ckTrue, sizeof(ckTrue) }, { CKA_SIGN, &ckTrue, sizeof(ckTrue) }, { CKA_VERIFY, &ckTrue, sizeof(ckTrue) }, + { CKA_DERIVE, &ckTrue, sizeof(ckTrue) }, #ifndef NO_AES { CKA_VALUE, aes_128_key, sizeof(aes_128_key) }, #endif @@ -5273,6 +5275,18 @@ static CK_RV test_digest_fail(void* args) CHECK_CKR_FAIL(ret, CKR_SESSION_HANDLE_INVALID, "Digest Key invalid session handle"); } + if (ret == CKR_OK) { + /* C_DigestKey must return CKR_OPERATION_NOT_INITIALIZED before any + * other validation when C_DigestInit has not been called. */ + ret = funcList->C_DigestKey(session, key); + CHECK_CKR_FAIL(ret, CKR_OPERATION_NOT_INITIALIZED, + "Digest Key without DigestInit"); + } + if (ret == CKR_OK) { + /* Now initialize and exercise the invalid-object-handle path. */ + ret = funcList->C_DigestInit(session, &mech); + CHECK_CKR(ret, "Digest Init for invalid-handle case"); + } if (ret == CKR_OK) { ret = funcList->C_DigestKey(session, CK_INVALID_HANDLE); CHECK_CKR_FAIL(ret, CKR_OBJECT_HANDLE_INVALID, @@ -8798,6 +8812,7 @@ static CK_OBJECT_HANDLE get_ecc_priv_key(CK_SESSION_HANDLE session, { CKA_SENSITIVE, &ckFalse, sizeof(ckFalse) }, { CKA_EXTRACTABLE, &extractable, sizeof(CK_BBOOL) }, { CKA_VERIFY, &ckTrue, sizeof(ckTrue) }, + { CKA_DERIVE, &ckTrue, sizeof(ckTrue) }, { CKA_EC_PARAMS, ecc_p256_params, sizeof(ecc_p256_params) }, { CKA_VALUE, ecc_p256_priv, sizeof(ecc_p256_priv) }, }; @@ -10216,6 +10231,7 @@ static CK_RV gen_aes_key(CK_SESSION_HANDLE session, int len, unsigned char* id, CK_ULONG keyLen = len; CK_ATTRIBUTE keyTmpl[] = { { CKA_VALUE_LEN, &keyLen, sizeof(keyLen) }, + { CKA_DERIVE, &ckTrue, sizeof(ckTrue) }, { CKA_TOKEN, &token, sizeof(token) }, { CKA_ID, id, idLen }, }; @@ -14856,12 +14872,14 @@ static CK_RV test_hkdf_derive_extract_then_expand_salt_data(void* args) CK_MECHANISM mechanism = { CKM_HKDF_DERIVE, ¶ms, sizeof(params) }; - /* Template for the derived key (PRK) */ + /* Template for the derived key (PRK). CKA_DERIVE=CK_TRUE so the PRK can + * itself serve as the base key for the subsequent Expand call. */ CK_ATTRIBUTE template[] = { {CKA_CLASS, &secretKeyClass, sizeof(secretKeyClass)}, {CKA_KEY_TYPE, &genericKeyType, sizeof(genericKeyType)}, {CKA_SENSITIVE, &ckFalse, sizeof(ckFalse)}, {CKA_EXTRACTABLE, &ckTrue, sizeof(ckTrue)}, + {CKA_DERIVE, &ckTrue, sizeof(ckTrue)}, {CKA_VALUE_LEN, &derived_len, sizeof(derived_len)} }; CK_ULONG template_count = sizeof(template) / sizeof(template[0]); @@ -15475,6 +15493,7 @@ static CK_RV test_hkdf_derive_expand_null_value_len(void* args) {CKA_KEY_TYPE, &genericKeyType, sizeof(genericKeyType)}, {CKA_SENSITIVE, &ckFalse, sizeof(ckFalse)}, {CKA_EXTRACTABLE, &ckTrue, sizeof(ckTrue)}, + {CKA_DERIVE, &ckTrue, sizeof(ckTrue)}, {CKA_VALUE_LEN, &prk_len, sizeof(prk_len)} }; CK_ULONG templateExtractCount = @@ -17207,9 +17226,451 @@ static CK_RV test_encrypt_data_len_range(void* args) return CKR_SKIPPED; #endif } + +/* After an early-return error in C_Encrypt (e.g. CKR_DATA_LEN_RANGE) the + * session must be back to "no active operation" so that a fresh + * C_EncryptInit succeeds. Before the fix the session stayed in + * WP11_OP_ENCRYPT and re-init returned CKR_OPERATION_ACTIVE. */ +static CK_RV test_op_active_after_data_len_range(void* args) +{ +#if SIZEOF_LONG > 4 + CK_SESSION_HANDLE session = *(CK_SESSION_HANDLE*)args; + CK_RV ret; + CK_OBJECT_HANDLE key; + byte plain[16], enc[32], iv[16]; + CK_ULONG encSz; + CK_MECHANISM mech; + + memset(plain, 9, sizeof(plain)); + memset(iv, 9, sizeof(iv)); + + mech.mechanism = CKM_AES_CBC; + mech.ulParameterLen = sizeof(iv); + mech.pParameter = iv; + + ret = get_aes_128_key(session, NULL, 0, &key); + if (ret == CKR_OK) { + ret = funcList->C_EncryptInit(session, &mech, key); + CHECK_CKR(ret, "Encrypt Init for op-active recovery test"); + } + if (ret == CKR_OK) { + CK_ULONG bigLen = ((CK_ULONG)1 << 32) + 16; + encSz = sizeof(enc); + ret = funcList->C_Encrypt(session, plain, bigLen, enc, &encSz); + CHECK_CKR_FAIL(ret, CKR_DATA_LEN_RANGE, + "Encrypt rejects oversized data length"); + } + /* Re-init must succeed: the previous error must have terminated the + * active operation. */ + if (ret == CKR_OK) { + ret = funcList->C_EncryptInit(session, &mech, key); + CHECK_CKR(ret, "Encrypt Init succeeds after CKR_DATA_LEN_RANGE"); + } + /* Clean up the active operation. */ + if (ret == CKR_OK) { + encSz = sizeof(enc); + ret = funcList->C_Encrypt(session, plain, sizeof(plain), enc, &encSz); + CHECK_CKR(ret, "Encrypt completes normally after re-init"); + } + + return ret; +#else + (void)args; + return CKR_SKIPPED; +#endif +} + +/* C_EncryptUpdate / C_DecryptUpdate must terminate the active op when they + * return CKR_DATA_LEN_RANGE so the next C_EncryptInit/C_DecryptInit succeeds. + * Forces the failure via the CK_ULONG_FITS_WORD32 path on AES-CBC (only + * meaningful on 64-bit). */ +static CK_RV test_op_active_after_update_data_len_range(void* args) +{ +#if SIZEOF_LONG > 4 + CK_SESSION_HANDLE session = *(CK_SESSION_HANDLE*)args; + CK_RV ret; + CK_OBJECT_HANDLE key; + byte plain[16], enc[32], iv[16]; + CK_ULONG encSz; + CK_MECHANISM mech; + CK_ULONG bigLen = ((CK_ULONG)1 << 32) + 16; + + memset(plain, 0xA5, sizeof(plain)); + memset(iv, 0x5A, sizeof(iv)); + + mech.mechanism = CKM_AES_CBC; + mech.ulParameterLen = sizeof(iv); + mech.pParameter = iv; + + ret = get_aes_128_key(session, NULL, 0, &key); + + /* EncryptUpdate path */ + if (ret == CKR_OK) { + ret = funcList->C_EncryptInit(session, &mech, key); + CHECK_CKR(ret, "EncryptInit for EncryptUpdate recovery test"); + } + if (ret == CKR_OK) { + encSz = sizeof(enc); + ret = funcList->C_EncryptUpdate(session, plain, bigLen, enc, &encSz); + CHECK_CKR_FAIL(ret, CKR_DATA_LEN_RANGE, + "EncryptUpdate rejects oversized part length"); + } + if (ret == CKR_OK) { + ret = funcList->C_EncryptInit(session, &mech, key); + CHECK_CKR(ret, "EncryptInit succeeds after EncryptUpdate failure"); + } + if (ret == CKR_OK) { + encSz = sizeof(enc); + ret = funcList->C_Encrypt(session, plain, sizeof(plain), enc, &encSz); + CHECK_CKR(ret, "Encrypt drains the recovered op"); + } + + /* DecryptUpdate path */ + if (ret == CKR_OK) { + ret = funcList->C_DecryptInit(session, &mech, key); + CHECK_CKR(ret, "DecryptInit for DecryptUpdate recovery test"); + } + if (ret == CKR_OK) { + encSz = sizeof(enc); + ret = funcList->C_DecryptUpdate(session, plain, bigLen, enc, &encSz); + CHECK_CKR_FAIL(ret, CKR_DATA_LEN_RANGE, + "DecryptUpdate rejects oversized part length"); + } + if (ret == CKR_OK) { + ret = funcList->C_DecryptInit(session, &mech, key); + CHECK_CKR(ret, "DecryptInit succeeds after DecryptUpdate failure"); + } + /* Drain by aborting via a fresh init pair. */ + if (ret == CKR_OK) { + ret = funcList->C_EncryptInit(session, &mech, key); + CHECK_CKR(ret, "EncryptInit drains pending decrypt op"); + } + if (ret == CKR_OK) { + encSz = sizeof(enc); + ret = funcList->C_Encrypt(session, plain, sizeof(plain), enc, &encSz); + CHECK_CKR(ret, "Encrypt completes drain"); + } + + return ret; +#else + (void)args; + return CKR_SKIPPED; +#endif +} #endif /* HAVE_AES_CBC */ #endif /* !NO_AES */ +#ifndef NO_HMAC +#ifndef NO_SHA256 +/* C_SignUpdate / C_VerifyUpdate must terminate the active op when they hit + * the unsupported-mechanism path so a fresh SignInit/VerifyInit succeeds. + * Force the failure by initializing the op with an RSA mechanism (which is + * single-part only; SignUpdate/VerifyUpdate's switch hits the default + * CKR_MECHANISM_INVALID branch). */ +#if !defined(NO_RSA) +static CK_RV test_op_active_after_sign_verify_update_failure(void* args) +{ + CK_SESSION_HANDLE session = *(CK_SESSION_HANDLE*)args; + CK_RV ret; + CK_OBJECT_HANDLE priv = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE pub = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE hmacKey = CK_INVALID_HANDLE; + CK_MECHANISM rsaMech; + CK_MECHANISM hmacMech; + byte data[32]; + byte sig[2048/8]; + CK_ULONG sigSz = sizeof(sig); + + memset(data, 0x5A, sizeof(data)); + rsaMech.mechanism = CKM_RSA_PKCS; + rsaMech.pParameter = NULL; + rsaMech.ulParameterLen = 0; + hmacMech.mechanism = CKM_SHA256_HMAC; + hmacMech.pParameter = NULL; + hmacMech.ulParameterLen = 0; + + ret = get_rsa_priv_key(session, NULL, 0, CK_FALSE, &priv); + if (ret == CKR_OK) + ret = get_rsa_pub_key(session, NULL, 0, &pub); + if (ret == CKR_OK) + ret = get_generic_key(session, data, sizeof(data), CK_TRUE, &hmacKey); + + /* SignUpdate path: RSA-init then SignUpdate -> CKR_MECHANISM_INVALID. */ + if (ret == CKR_OK) { + ret = funcList->C_SignInit(session, &rsaMech, priv); + CHECK_CKR(ret, "SignInit with RSA for SignUpdate recovery test"); + } + if (ret == CKR_OK) { + ret = funcList->C_SignUpdate(session, data, sizeof(data)); + CHECK_CKR_FAIL(ret, CKR_MECHANISM_INVALID, + "SignUpdate rejected for single-part-only mechanism"); + } + if (ret == CKR_OK) { + ret = funcList->C_SignInit(session, &hmacMech, hmacKey); + CHECK_CKR(ret, "SignInit succeeds after SignUpdate failure"); + } + if (ret == CKR_OK) { + sigSz = sizeof(sig); + ret = funcList->C_Sign(session, data, sizeof(data), sig, &sigSz); + CHECK_CKR(ret, "Sign drains the recovered op"); + } + + /* VerifyUpdate path: same shape. */ + if (ret == CKR_OK) { + ret = funcList->C_VerifyInit(session, &rsaMech, pub); + CHECK_CKR(ret, "VerifyInit with RSA for VerifyUpdate recovery test"); + } + if (ret == CKR_OK) { + ret = funcList->C_VerifyUpdate(session, data, sizeof(data)); + CHECK_CKR_FAIL(ret, CKR_MECHANISM_INVALID, + "VerifyUpdate rejected for single-part-only mechanism"); + } + if (ret == CKR_OK) { + ret = funcList->C_VerifyInit(session, &hmacMech, hmacKey); + CHECK_CKR(ret, "VerifyInit succeeds after VerifyUpdate failure"); + } + if (ret == CKR_OK) { + ret = funcList->C_Verify(session, data, sizeof(data), sig, sigSz); + CHECK_CKR(ret, "Verify drains the recovered op"); + } + + return ret; +} +#endif /* !NO_RSA */ + +/* C_DigestKey must terminate the digest op on Object_Find failure so a + * fresh C_DigestInit succeeds. */ +static CK_RV test_op_active_after_digest_key_failure(void* args) +{ + CK_SESSION_HANDLE session = *(CK_SESSION_HANDLE*)args; + CK_RV ret; + CK_MECHANISM mech; + byte digest[32]; + CK_ULONG digestSz; + byte data[16]; + + mech.mechanism = CKM_SHA256; + mech.pParameter = NULL; + mech.ulParameterLen = 0; + memset(data, 0xC3, sizeof(data)); + + ret = funcList->C_DigestInit(session, &mech); + CHECK_CKR(ret, "DigestInit for DigestKey recovery test"); + + if (ret == CKR_OK) { + /* Invalid object handle -> CKR_OBJECT_HANDLE_INVALID. The fix must + * also terminate the active digest. */ + ret = funcList->C_DigestKey(session, CK_INVALID_HANDLE); + CHECK_CKR_FAIL(ret, CKR_OBJECT_HANDLE_INVALID, + "DigestKey rejects invalid handle"); + } + if (ret == CKR_OK) { + ret = funcList->C_DigestInit(session, &mech); + CHECK_CKR(ret, "DigestInit succeeds after DigestKey failure"); + } + if (ret == CKR_OK) { + digestSz = sizeof(digest); + ret = funcList->C_Digest(session, data, sizeof(data), digest, &digestSz); + CHECK_CKR(ret, "Digest drains the recovered op"); + } + + return ret; +} + +#ifdef WOLFPKCS11_NO_STORE +/* On WOLFPKCS11_NO_STORE builds, WP11_Digest_Key returns the positive + * CK_RV CKR_FUNCTION_NOT_SUPPORTED. C_DigestKey must propagate that code + * (not clobber it to CKR_FUNCTION_FAILED) and must still terminate the + * digest operation so a fresh DigestInit succeeds. */ +static CK_RV test_op_active_after_digest_key_no_store(void* args) +{ + CK_SESSION_HANDLE session = *(CK_SESSION_HANDLE*)args; + CK_RV ret; + CK_MECHANISM mech; + CK_OBJECT_HANDLE key = CK_INVALID_HANDLE; + CK_ULONG valueLen = 16; + byte keyData[16]; + byte digest[32]; + CK_ULONG digestSz; + byte data[16]; + CK_ATTRIBUTE keyTmpl[] = { + { CKA_CLASS, &secretKeyClass, sizeof(secretKeyClass) }, + { CKA_KEY_TYPE, &genericKeyType, sizeof(genericKeyType) }, + { CKA_VALUE, keyData, sizeof(keyData) }, + { CKA_VALUE_LEN, &valueLen, sizeof(valueLen) }, + }; + CK_ULONG keyTmplCnt = sizeof(keyTmpl) / sizeof(*keyTmpl); + + mech.mechanism = CKM_SHA256; + mech.pParameter = NULL; + mech.ulParameterLen = 0; + memset(data, 0xC3, sizeof(data)); + memset(keyData, 0x5A, sizeof(keyData)); + + ret = funcList->C_CreateObject(session, keyTmpl, keyTmplCnt, &key); + CHECK_CKR(ret, "Create secret key for NO_STORE DigestKey test"); + + if (ret == CKR_OK) { + ret = funcList->C_DigestInit(session, &mech); + CHECK_CKR(ret, "DigestInit for NO_STORE DigestKey test"); + } + if (ret == CKR_OK) { + ret = funcList->C_DigestKey(session, key); + CHECK_CKR_FAIL(ret, CKR_FUNCTION_NOT_SUPPORTED, + "DigestKey on NO_STORE build returns " + "CKR_FUNCTION_NOT_SUPPORTED"); + } + if (ret == CKR_OK) { + ret = funcList->C_DigestInit(session, &mech); + CHECK_CKR(ret, "DigestInit succeeds after NO_STORE DigestKey failure"); + } + if (ret == CKR_OK) { + digestSz = sizeof(digest); + ret = funcList->C_Digest(session, data, sizeof(data), digest, &digestSz); + CHECK_CKR(ret, "Digest drains the recovered op"); + } + + if (key != CK_INVALID_HANDLE) + funcList->C_DestroyObject(session, key); + return ret; +} +#endif /* WOLFPKCS11_NO_STORE */ +#endif /* !NO_SHA256 */ +#endif /* !NO_HMAC */ + +/* CKA_COPYABLE=CK_FALSE must cause C_CopyObject to return + * CKR_ACTION_PROHIBITED (PKCS#11 v2.40 sec. 11.7.5). */ +static CK_RV test_copy_object_not_copyable(void* args) +{ + CK_SESSION_HANDLE session = *(CK_SESSION_HANDLE*)args; + CK_RV ret; + CK_OBJECT_HANDLE src = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE copy = CK_INVALID_HANDLE; + CK_ULONG valueLen = 32; + byte keyData[32]; + CK_BBOOL writableCopyable = CK_TRUE; + CK_ATTRIBUTE writableTmpl[] = { + { CKA_COPYABLE, &writableCopyable, sizeof(writableCopyable) }, + }; + CK_ATTRIBUTE tmpl[] = { + { CKA_CLASS, &secretKeyClass, sizeof(secretKeyClass) }, + { CKA_KEY_TYPE, &genericKeyType, sizeof(genericKeyType) }, + { CKA_VALUE, keyData, sizeof(keyData) }, + { CKA_VALUE_LEN, &valueLen, sizeof(valueLen) }, + { CKA_COPYABLE, &ckFalse, sizeof(ckFalse) }, + }; + CK_ULONG tmplCnt = sizeof(tmpl) / sizeof(*tmpl); + + memset(keyData, 0xA5, sizeof(keyData)); + + ret = funcList->C_CreateObject(session, tmpl, tmplCnt, &src); + CHECK_CKR(ret, "Create non-copyable object"); + if (ret == CKR_OK) { + ret = funcList->C_CopyObject(session, src, NULL, 0, ©); + CHECK_CKR_FAIL(ret, CKR_ACTION_PROHIBITED, + "Copy of CKA_COPYABLE=FALSE object rejected"); + if (copy != CK_INVALID_HANDLE) + funcList->C_DestroyObject(session, copy); + /* PKCS#11 v2.40 sec 4.4.1: FALSE->TRUE flip must be rejected. */ + ret = funcList->C_SetAttributeValue(session, src, writableTmpl, 1); + CHECK_CKR_FAIL(ret, CKR_ATTRIBUTE_READ_ONLY, + "Flip CKA_COPYABLE FALSE->TRUE rejected"); + } + if (src != CK_INVALID_HANDLE) + funcList->C_DestroyObject(session, src); + return ret; +} + +/* CKA_DESTROYABLE=CK_FALSE must cause C_DestroyObject to return + * CKR_ACTION_PROHIBITED, and a subsequent attempt to flip CKA_DESTROYABLE + * back to CK_TRUE must be rejected per PKCS#11 v2.40 sec. 4.4.1. The + * object is a session object, so the harness's C_CloseSession will free + * it on test teardown. */ +static CK_RV test_destroy_object_not_destroyable(void* args) +{ + CK_SESSION_HANDLE session = *(CK_SESSION_HANDLE*)args; + CK_RV ret; + CK_OBJECT_HANDLE obj = CK_INVALID_HANDLE; + CK_BBOOL writableDestroyable = CK_TRUE; + CK_ATTRIBUTE writableTmpl[] = { + { CKA_DESTROYABLE, &writableDestroyable, sizeof(writableDestroyable) }, + }; + CK_ULONG valueLen = 32; + byte keyData[32]; + CK_ATTRIBUTE tmpl[] = { + { CKA_CLASS, &secretKeyClass, sizeof(secretKeyClass) }, + { CKA_KEY_TYPE, &genericKeyType, sizeof(genericKeyType) }, + { CKA_VALUE, keyData, sizeof(keyData) }, + { CKA_VALUE_LEN, &valueLen, sizeof(valueLen) }, + { CKA_DESTROYABLE, &ckFalse, sizeof(ckFalse) }, + }; + CK_ULONG tmplCnt = sizeof(tmpl) / sizeof(*tmpl); + + memset(keyData, 0x5A, sizeof(keyData)); + + ret = funcList->C_CreateObject(session, tmpl, tmplCnt, &obj); + CHECK_CKR(ret, "Create non-destroyable object"); + if (ret == CKR_OK) { + ret = funcList->C_DestroyObject(session, obj); + CHECK_CKR_FAIL(ret, CKR_ACTION_PROHIBITED, + "Destroy of CKA_DESTROYABLE=FALSE object rejected"); + /* Verify FALSE->TRUE flip is also rejected. */ + ret = funcList->C_SetAttributeValue(session, obj, writableTmpl, 1); + CHECK_CKR_FAIL(ret, CKR_ATTRIBUTE_READ_ONLY, + "Flip CKA_DESTROYABLE FALSE->TRUE rejected"); + } + return ret; +} + +/* CKA_DERIVE=CK_FALSE must cause C_DeriveKey to reject the base key + * (CKR_KEY_TYPE_INCONSISTENT via the existing CheckOpSupported pattern). + * The check is skipped on WOLFPKCS11_NSS builds for NSS compatibility. */ +#if !defined(NO_DH) && !defined(WOLFPKCS11_NSS) +static CK_RV test_derive_key_not_allowed(void* args) +{ + CK_SESSION_HANDLE session = *(CK_SESSION_HANDLE*)args; + CK_RV ret; + byte peer[32]; + CK_OBJECT_HANDLE base = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE secret = CK_INVALID_HANDLE; + CK_KEY_TYPE keyType = CKK_GENERIC_SECRET; + CK_ULONG secSz = 32; + CK_ATTRIBUTE outTmpl[] = { + { CKA_CLASS, &secretKeyClass, sizeof(secretKeyClass) }, + { CKA_KEY_TYPE, &keyType, sizeof(keyType) }, + { CKA_VALUE_LEN, &secSz, sizeof(secSz) }, + }; + CK_ULONG outTmplCnt = sizeof(outTmpl) / sizeof(*outTmpl); + CK_ATTRIBUTE baseTmpl[] = { + { CKA_CLASS, &secretKeyClass, sizeof(secretKeyClass) }, + { CKA_KEY_TYPE, &genericKeyType, sizeof(genericKeyType) }, + { CKA_VALUE, peer, sizeof(peer) }, + { CKA_DERIVE, &ckFalse, sizeof(ckFalse) }, + }; + CK_ULONG baseTmplCnt = sizeof(baseTmpl) / sizeof(*baseTmpl); + CK_MECHANISM mech; + + memset(peer, 9, sizeof(peer)); + mech.mechanism = CKM_DH_PKCS_DERIVE; + mech.ulParameterLen = sizeof(peer); + mech.pParameter = peer; + + ret = funcList->C_CreateObject(session, baseTmpl, baseTmplCnt, &base); + CHECK_CKR(ret, "Create base key with CKA_DERIVE=FALSE"); + if (ret == CKR_OK) { + ret = funcList->C_DeriveKey(session, &mech, base, outTmpl, outTmplCnt, + &secret); + CHECK_CKR_FAIL(ret, CKR_KEY_TYPE_INCONSISTENT, + "DeriveKey rejected when base CKA_DERIVE=FALSE"); + if (secret != CK_INVALID_HANDLE) + funcList->C_DestroyObject(session, secret); + } + if (base != CK_INVALID_HANDLE) + funcList->C_DestroyObject(session, base); + return ret; +} +#endif /* !NO_DH && !WOLFPKCS11_NSS */ + #if !defined(NO_RSA) && !defined(WC_NO_RSA_OAEP) /* Calling C_EncryptInit with OAEP twice in a row without completing the first * operation exercises the re-initialization path in SetOaepParams. Any label @@ -17457,6 +17918,11 @@ static TEST_FUNC testFunc[] = { PKCS11TEST_FUNC_SESS_DECL(test_create_session_obj_ro_session), #endif PKCS11TEST_FUNC_SESS_DECL(test_copy_object_deep_copy), + PKCS11TEST_FUNC_SESS_DECL(test_copy_object_not_copyable), + PKCS11TEST_FUNC_SESS_DECL(test_destroy_object_not_destroyable), +#if !defined(NO_DH) && !defined(WOLFPKCS11_NSS) + PKCS11TEST_FUNC_SESS_DECL(test_derive_key_not_allowed), +#endif #if (!defined(NO_RSA) && !defined(WOLFPKCS11_TPM) && defined(WOLFSSL_KEY_GEN)) PKCS11TEST_FUNC_SESS_DECL(test_copy_object_rsa_key), #endif @@ -17609,6 +18075,8 @@ static TEST_FUNC testFunc[] = { PKCS11TEST_FUNC_SESS_DECL(test_aes_cbc_pad_gen_key), PKCS11TEST_FUNC_SESS_DECL(test_aes_cbc_pad_gen_key_id), PKCS11TEST_FUNC_SESS_DECL(test_encrypt_data_len_range), + PKCS11TEST_FUNC_SESS_DECL(test_op_active_after_data_len_range), + PKCS11TEST_FUNC_SESS_DECL(test_op_active_after_update_data_len_range), #endif #ifdef HAVE_AESCTR PKCS11TEST_FUNC_SESS_DECL(test_aes_ctr_fixed_key), @@ -17670,6 +18138,13 @@ static TEST_FUNC testFunc[] = { PKCS11TEST_FUNC_SESS_DECL(test_hmac_sha256), PKCS11TEST_FUNC_SESS_DECL(test_hmac_sha256_fail), PKCS11TEST_FUNC_SESS_DECL(test_hmac_sha256_truncated_sig), +#if !defined(NO_RSA) + PKCS11TEST_FUNC_SESS_DECL(test_op_active_after_sign_verify_update_failure), +#endif + PKCS11TEST_FUNC_SESS_DECL(test_op_active_after_digest_key_failure), +#ifdef WOLFPKCS11_NO_STORE + PKCS11TEST_FUNC_SESS_DECL(test_op_active_after_digest_key_no_store), +#endif #endif #ifdef WOLFSSL_SHA384 PKCS11TEST_FUNC_SESS_DECL(test_hmac_sha384), diff --git a/tests/pkcs11v3test.c b/tests/pkcs11v3test.c index 084ffb18..23aa4930 100644 --- a/tests/pkcs11v3test.c +++ b/tests/pkcs11v3test.c @@ -2020,14 +2020,16 @@ static CK_RV test_mlkem_key_validation(void* args) ret = funcListExt->C_EncapsulateKey(session, &mech, mldsaPub, secretTmpl, secretTmplCnt, NULL, &ctLen, &secret); - CHECK_CKR_FAIL(ret, CKR_KEY_TYPE_INCONSISTENT, + /* ML-DSA public keys default CKA_ENCAPSULATE to CK_FALSE, so the + * CKA_ENCAPSULATE gate fires before the key-type/mechanism check. */ + CHECK_CKR_FAIL(ret, CKR_KEY_FUNCTION_NOT_PERMITTED, "ML-KEM Encapsulate wrong key type"); } if (ret == CKR_OK) { ret = funcListExt->C_DecapsulateKey(session, &mech, mldsaPriv, secretTmpl, secretTmplCnt, dummyCt, sizeof(dummyCt), &secret); - CHECK_CKR_FAIL(ret, CKR_KEY_TYPE_INCONSISTENT, + CHECK_CKR_FAIL(ret, CKR_KEY_FUNCTION_NOT_PERMITTED, "ML-KEM Decapsulate wrong key type"); } #endif @@ -2161,6 +2163,80 @@ static CK_RV test_mlkem_initial_states(void* args) return ret; } +/* CKA_ENCAPSULATE=CK_FALSE on the public key must reject C_EncapsulateKey; + * CKA_DECAPSULATE=CK_FALSE on the private key must reject C_DecapsulateKey. + * Both return CKR_KEY_FUNCTION_NOT_PERMITTED per PKCS#11 v3.2 sec. 6.5. */ +static CK_RV test_mlkem_encap_decap_not_permitted(void* args) +{ + CK_SESSION_HANDLE session = *(CK_SESSION_HANDLE*)args; + CK_RV ret = CKR_OK; + CK_FUNCTION_LIST_3_2* funcListExt = (CK_FUNCTION_LIST_3_2*)funcList; + CK_OBJECT_HANDLE pub = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE priv = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE secret = CK_INVALID_HANDLE; + CK_MECHANISM mech; + CK_OBJECT_CLASS secretClass = CKO_SECRET_KEY; + CK_KEY_TYPE genericKeyType = CKK_GENERIC_SECRET; + CK_BBOOL extractable = CK_TRUE; + CK_BBOOL encapFlag = CK_FALSE; + CK_BBOOL decapFlag = CK_FALSE; + CK_ATTRIBUTE secretTmpl[] = { + { CKA_CLASS, &secretClass, sizeof(secretClass) }, + { CKA_KEY_TYPE, &genericKeyType, sizeof(genericKeyType) }, + { CKA_EXTRACTABLE, &extractable, sizeof(extractable) }, + }; + CK_ULONG secretTmplCnt = sizeof(secretTmpl) / sizeof(*secretTmpl); + CK_ATTRIBUTE encapAttr[] = { + { CKA_ENCAPSULATE, &encapFlag, sizeof(encapFlag) }, + }; + CK_ATTRIBUTE decapAttr[] = { + { CKA_DECAPSULATE, &decapFlag, sizeof(decapFlag) }, + }; + CK_ULONG ctLen = 0; + CK_BYTE dummyCt[1] = { 0 }; + + ret = gen_mlkem_keys(session, CKP_ML_KEM_512, &pub, &priv, NULL, 0, + NULL, 0, 0); + + /* Flip CKA_ENCAPSULATE / CKA_DECAPSULATE off on the generated keys. */ + if (ret == CKR_OK) { + ret = funcList->C_SetAttributeValue(session, pub, encapAttr, 1); + CHECK_CKR(ret, "Set CKA_ENCAPSULATE=FALSE on ML-KEM pub"); + } + if (ret == CKR_OK) { + ret = funcList->C_SetAttributeValue(session, priv, decapAttr, 1); + CHECK_CKR(ret, "Set CKA_DECAPSULATE=FALSE on ML-KEM priv"); + } + + mech.mechanism = CKM_ML_KEM; + mech.pParameter = NULL; + mech.ulParameterLen = 0; + + if (ret == CKR_OK) { + ret = funcListExt->C_EncapsulateKey(session, &mech, pub, secretTmpl, + secretTmplCnt, NULL, &ctLen, + &secret); + CHECK_CKR_FAIL(ret, CKR_KEY_FUNCTION_NOT_PERMITTED, + "Encapsulate rejected when CKA_ENCAPSULATE=FALSE"); + } + if (ret == CKR_OK) { + ret = funcListExt->C_DecapsulateKey(session, &mech, priv, secretTmpl, + secretTmplCnt, dummyCt, + sizeof(dummyCt), &secret); + CHECK_CKR_FAIL(ret, CKR_KEY_FUNCTION_NOT_PERMITTED, + "Decapsulate rejected when CKA_DECAPSULATE=FALSE"); + } + + if (priv != CK_INVALID_HANDLE) + funcList->C_DestroyObject(session, priv); + if (pub != CK_INVALID_HANDLE) + funcList->C_DestroyObject(session, pub); + if (secret != CK_INVALID_HANDLE) + funcList->C_DestroyObject(session, secret); + + return ret; +} + static CK_RV test_copy_object_mlkem_key(void* args) { CK_SESSION_HANDLE session = *(CK_SESSION_HANDLE*)args; @@ -2857,6 +2933,7 @@ static TEST_FUNC testFunc[] = { PKCS11TEST_FUNC_SESS_DECL(test_mlkem_bad_mech_params), PKCS11TEST_FUNC_SESS_DECL(test_mlkem_key_validation), PKCS11TEST_FUNC_SESS_DECL(test_mlkem_initial_states), + PKCS11TEST_FUNC_SESS_DECL(test_mlkem_encap_decap_not_permitted), PKCS11TEST_FUNC_SESS_DECL(test_copy_object_mlkem_key), #endif #endif diff --git a/tests/so_login_uninit_test.c b/tests/so_login_uninit_test.c new file mode 100644 index 00000000..c504aea3 --- /dev/null +++ b/tests/so_login_uninit_test.c @@ -0,0 +1,242 @@ +/* so_login_uninit_test.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfPKCS11. + * + * wolfPKCS11 is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfPKCS11 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + * + * Regression test for the SO PIN empty-PIN bypass that + * WP11_Slot_CheckSOPin used to allow when the SO PIN had not been set. + * Before the fix, C_Login(CKU_SO, "", 0) on a fresh (uninitialized) token + * would succeed because WP11_ConstantCompare(., ., 0) returns true. This + * test exercises that exact path and asserts the call now fails with + * CKR_USER_PIN_NOT_INITIALIZED. + */ + +#ifdef HAVE_CONFIG_H + #include +#endif + +#include +#include + +#ifndef WOLFSSL_USER_SETTINGS + #include +#endif +#include +#include + +#ifndef WOLFPKCS11_USER_SETTINGS + #include +#endif +#include + +#ifndef HAVE_PKCS11_STATIC +#include +#endif + +#include "testdata.h" + +#define SO_TEST_DIR "./store/so_login_uninit_test" +#define WOLFPKCS11_TOKEN_FILENAME "wp11_token_0000000000000001" + +static int test_passed = 0; +static int test_failed = 0; + +#define CHECK_RV(rv, op, expected) do { \ + if ((rv) != (expected)) { \ + fprintf(stderr, "FAIL: %s: expected 0x%lx, got 0x%lx\n", op, \ + (unsigned long)(expected), (unsigned long)(rv)); \ + test_failed++; \ + } else { \ + printf("PASS: %s\n", op); \ + test_passed++; \ + } \ +} while (0) + +/* Empty-PIN may be rejected by C_Login's length check before reaching the + * SO-PIN check, or by the fix in WP11_Slot_CheckSOPin. Either rejection + * proves the bypass is closed. */ +#define CHECK_EMPTY_REJECTED(rv, op) do { \ + if ((rv) != CKR_USER_PIN_NOT_INITIALIZED && \ + (rv) != CKR_PIN_INCORRECT && \ + (rv) != CKR_PIN_LEN_RANGE) { \ + fprintf(stderr, "FAIL: %s: empty PIN accepted with 0x%lx\n", op, \ + (unsigned long)(rv)); \ + test_failed++; \ + } else { \ + printf("PASS: %s (rv=0x%lx)\n", op, (unsigned long)(rv)); \ + test_passed++; \ + } \ +} while (0) + +#ifndef HAVE_PKCS11_STATIC +static void* dlib; +#endif +static CK_FUNCTION_LIST* funcList; + +static CK_RV pkcs11_load(void) +{ + CK_RV ret; + +#ifndef HAVE_PKCS11_STATIC + CK_C_GetFunctionList func; + + dlib = dlopen(WOLFPKCS11_DLL_FILENAME, RTLD_NOW | RTLD_LOCAL); + if (dlib == NULL) { + fprintf(stderr, "dlopen error: %s\n", dlerror()); + return CKR_GENERAL_ERROR; + } + func = (CK_C_GetFunctionList)dlsym(dlib, "C_GetFunctionList"); + if (func == NULL) { + fprintf(stderr, "Failed to get function list function\n"); + dlclose(dlib); + return CKR_GENERAL_ERROR; + } + ret = func(&funcList); + if (ret != CKR_OK) { + dlclose(dlib); + return ret; + } +#else + ret = C_GetFunctionList(&funcList); + if (ret != CKR_OK) + return ret; +#endif + return CKR_OK; +} + +static void pkcs11_unload(void) +{ +#ifndef HAVE_PKCS11_STATIC + if (dlib != NULL) { + dlclose(dlib); + dlib = NULL; + } +#endif + funcList = NULL; +} + +static void cleanup_store(const char* dir) +{ + char filepath[512]; + snprintf(filepath, sizeof(filepath), "%s" PATH_SEP "%s", dir, + WOLFPKCS11_TOKEN_FILENAME); + (void)remove(filepath); +} + +static int run_test(void) +{ + CK_RV rv; + CK_C_INITIALIZE_ARGS args; + CK_SLOT_ID slotList[16]; + CK_ULONG slotCount = sizeof(slotList) / sizeof(slotList[0]); + CK_SLOT_ID slot = 0; + CK_SESSION_HANDLE session = 0; + int sessFlags = CKF_SERIAL_SESSION | CKF_RW_SESSION; + + /* Drop any token file so the token loads in an uninitialized state + * with WP11_TOKEN_FLAG_SO_PIN_SET clear. */ + cleanup_store(SO_TEST_DIR); + + rv = pkcs11_load(); + CHECK_RV(rv, "load library", CKR_OK); + if (rv != CKR_OK) + return -1; + + XMEMSET(&args, 0, sizeof(args)); + args.flags = CKF_OS_LOCKING_OK; + rv = funcList->C_Initialize(&args); + CHECK_RV(rv, "C_Initialize", CKR_OK); + if (rv != CKR_OK) + goto out; + + rv = funcList->C_GetSlotList(CK_TRUE, slotList, &slotCount); + CHECK_RV(rv, "C_GetSlotList", CKR_OK); + if (rv != CKR_OK || slotCount == 0) + goto out; + slot = slotList[0]; + + rv = funcList->C_OpenSession(slot, sessFlags, NULL, NULL, &session); + CHECK_RV(rv, "C_OpenSession (uninit token)", CKR_OK); + if (rv != CKR_OK) + goto out; + +#ifndef WOLFPKCS11_NSS + /* Non-NSS builds: empty PIN against unset SO PIN must be rejected; + * pre-fix this succeeded because the zero-length constant-compare + * returned equal. Builds with WP11_MIN_PIN_LEN > 0 reject the length + * first; either rejection proves the bypass is closed. */ + rv = funcList->C_Login(session, CKU_SO, (CK_UTF8CHAR_PTR)"", 0); + CHECK_EMPTY_REJECTED(rv, "C_Login(CKU_SO, \"\", 0) rejected"); +#else + /* NSS builds intentionally accept the empty-PIN SO login on an + * uninitialized token so NSS's PK11_InitPin can bootstrap. Just + * verify that calling it does not return an error code that would + * cause NSS to abort. */ + rv = funcList->C_Login(session, CKU_SO, (CK_UTF8CHAR_PTR)"", 0); + if (rv != CKR_OK) { + fprintf(stderr, + "FAIL: NSS empty-PIN probe expected CKR_OK, got 0x%lx\n", + (unsigned long)rv); + test_failed++; + } else { + printf("PASS: NSS empty-PIN probe succeeds (bootstrap path)\n"); + test_passed++; + funcList->C_Logout(session); + } +#endif + + /* Non-empty PIN against unset SO PIN: must report + * CKR_USER_PIN_NOT_INITIALIZED rather than CKR_PIN_INCORRECT so the + * caller can distinguish "wrong PIN" from "no PIN set". This must hold + * in both NSS and non-NSS builds; the NSS empty-PIN bypass is + * length-zero only. */ + rv = funcList->C_Login(session, CKU_SO, + (CK_UTF8CHAR_PTR)"longerbadpin", 12); + CHECK_RV(rv, "C_Login(CKU_SO, non-empty, uninit) returns NOT_INITIALIZED", + CKR_USER_PIN_NOT_INITIALIZED); + +out: + if (session != 0) + funcList->C_CloseSession(session); + funcList->C_Finalize(NULL); + pkcs11_unload(); + return 0; +} + +int main(int argc, char* argv[]) +{ + (void)argc; + (void)argv; + +#ifndef WOLFPKCS11_NO_ENV + XSETENV("WOLFPKCS11_TOKEN_PATH", SO_TEST_DIR, 1); +#endif + + printf("=== wolfPKCS11 SO PIN uninit bypass test ===\n"); + run_test(); + + printf("\n=== Test Results ===\n"); + printf("Tests passed: %d\n", test_passed); + printf("Tests failed: %d\n", test_failed); + if (test_failed == 0) + printf("ALL TESTS PASSED!\n"); + else + printf("SOME TESTS FAILED!\n"); + + return (test_failed == 0) ? 0 : 1; +} diff --git a/wolfpkcs11/internal.h b/wolfpkcs11/internal.h index b7f6266f..f3d39db2 100644 --- a/wolfpkcs11/internal.h +++ b/wolfpkcs11/internal.h @@ -218,6 +218,10 @@ C_EXTRA_FLAGS="-DWOLFSSL_PUBLIC_MP -DWC_RSA_DIRECT" #define WP11_FLAG_DERIVE 0x00040000 #define WP11_FLAG_ENCAPSULATE 0x00080000 #define WP11_FLAG_DECAPSULATE 0x00100000 +/* These two flags invert their attribute's spec default (CK_TRUE). When the + * flag is set, the attribute reads as CK_FALSE; clear means CK_TRUE. */ +#define WP11_FLAG_NOT_COPYABLE 0x00200000 +#define WP11_FLAG_NOT_DESTROYABLE 0x00400000 /* Flags for token. */ #define WP11_TOKEN_FLAG_USER_PIN_SET 0x00000001 @@ -460,6 +464,8 @@ WP11_LOCAL int WP11_Object_DataObject(WP11_Object* object, unsigned char** data, WP11_LOCAL int WP11_Object_SetClass(WP11_Object* object, CK_OBJECT_CLASS objClass); WP11_LOCAL CK_OBJECT_CLASS WP11_Object_GetClass(WP11_Object* object); +WP11_LOCAL int WP11_Object_IsCopyable(WP11_Object* object); +WP11_LOCAL int WP11_Object_IsDestroyable(WP11_Object* object); #ifdef WOLFPKCS11_NSS WP11_LOCAL int WP11_Object_SetTrust(WP11_Object* object, unsigned char** data,