diff --git a/src/crypto.c b/src/crypto.c index 61182614..efb5fe20 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -479,7 +479,14 @@ static CK_RV CheckPrivateLogin(WP11_Session* session, return CKR_OK; if (WP11_Slot_Has_Empty_Pin(slot)) return CKR_OK; +#ifdef WOLFPKCS11_NSS + /* NSS operates as an internal crypto module and has separate private + * object semantics. Preserve its existing any-login behavior; F-8650 + * applies to the standard PKCS#11 session model. */ if (!WP11_Slot_IsLoggedIn(slot)) +#else + if (!WP11_Slot_IsUserLoggedIn(slot)) +#endif return CKR_USER_NOT_LOGGED_IN; return CKR_OK; } @@ -2147,7 +2154,7 @@ CK_RV C_SetAttributeValue(CK_SESSION_HANDLE hSession, /** * Initialize the finding of an object associated with the session. - * All matching objects are found, up to a limit, by this call. + * All matching objects are found by this call. * * @param hSession [in] Handle of session. * @param pTemplate [in] Template of attributes match against object. @@ -2167,6 +2174,7 @@ CK_RV C_FindObjectsInit(CK_SESSION_HANDLE hSession, CK_RV rv; WP11_Session* session; CK_ATTRIBUTE* attr; + int ret; int onToken = 1; WOLFPKCS11_ENTER("C_FindObjectsInit"); @@ -2192,8 +2200,9 @@ CK_RV C_FindObjectsInit(CK_SESSION_HANDLE hSession, return rv; } - if (WP11_Session_FindInit(session) != 0) { - rv = CKR_OPERATION_ACTIVE; + ret = WP11_Session_FindInit(session); + if (ret != 0) { + rv = ret == MEMORY_E ? CKR_HOST_MEMORY : CKR_OPERATION_ACTIVE; WOLFPKCS11_LEAVE("C_FindObjectsInit", rv); return rv; } @@ -2201,11 +2210,13 @@ CK_RV C_FindObjectsInit(CK_SESSION_HANDLE hSession, FindAttributeType(pTemplate, ulCount, CKA_TOKEN, &attr); if (attr != NULL) { if (attr->pValue == NULL) { + WP11_Session_FindFinal(session); rv = CKR_ATTRIBUTE_VALUE_INVALID; WOLFPKCS11_LEAVE("C_FindObjectsInit", rv); return rv; } if (attr->ulValueLen != sizeof(CK_BBOOL)) { + WP11_Session_FindFinal(session); rv = CKR_ATTRIBUTE_VALUE_INVALID; WOLFPKCS11_LEAVE("C_FindObjectsInit", rv); return rv; @@ -2213,7 +2224,13 @@ CK_RV C_FindObjectsInit(CK_SESSION_HANDLE hSession, onToken = *(CK_BBOOL*)attr->pValue; } - WP11_Session_Find(session, onToken, pTemplate, ulCount); + ret = WP11_Session_Find(session, onToken, pTemplate, ulCount); + if (ret != 0) { + WP11_Session_FindFinal(session); + rv = ret == MEMORY_E ? CKR_HOST_MEMORY : CKR_FUNCTION_FAILED; + WOLFPKCS11_LEAVE("C_FindObjectsInit", rv); + return rv; + } rv = CKR_OK; WOLFPKCS11_LEAVE("C_FindObjectsInit", rv); @@ -2829,6 +2846,8 @@ CK_RV C_Encrypt(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData, encDataLen = (word32)*pulEncryptedDataLen; ret = WP11_AesCtr_Do(pData, (word32)ulDataLen, pEncryptedData, &encDataLen, session); + if (ret == WP11_CTR_OVERFLOW_E) + return CKR_DATA_LEN_RANGE; if (ret != 0) break; *pulEncryptedDataLen = encDataLen; @@ -3140,6 +3159,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 == WP11_CTR_OVERFLOW_E) { + WP11_AesCtr_Final(session); + return CKR_DATA_LEN_RANGE; + } if (ret < 0) { WP11_Session_SetOpInitialized(session, 0); return CKR_FUNCTION_FAILED; @@ -3869,6 +3892,8 @@ CK_RV C_Decrypt(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pEncryptedData, decDataLen = (word32)*pulDataLen; ret = WP11_AesCtr_Do(pEncryptedData, (word32)ulEncryptedDataLen, pData, &decDataLen, session); + if (ret == WP11_CTR_OVERFLOW_E) + return CKR_DATA_LEN_RANGE; if (ret != 0) break; *pulDataLen = decDataLen; @@ -4024,7 +4049,6 @@ CK_RV C_Decrypt(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pEncryptedData, (word32)ulEncryptedDataLen, pData, &decDataLen, session); if (ret == BUFFER_E) { *pulDataLen = decDataLen; - WP11_Session_SetOpInitialized(session, 0); return CKR_BUFFER_TOO_SMALL; } if (ret != 0) @@ -4188,6 +4212,10 @@ CK_RV C_DecryptUpdate(CK_SESSION_HANDLE hSession, decPartLen = (word32)*pulPartLen; ret = WP11_AesCtr_Update(pEncryptedPart, (word32)ulEncryptedPartLen, pPart, &decPartLen, session); + if (ret == WP11_CTR_OVERFLOW_E) { + WP11_AesCtr_Final(session); + return CKR_DATA_LEN_RANGE; + } if (ret < 0) { WP11_Session_SetOpInitialized(session, 0); return CKR_FUNCTION_FAILED; diff --git a/src/internal.c b/src/internal.c index 85cc9d32..30cf800a 100644 --- a/src/internal.c +++ b/src/internal.c @@ -333,10 +333,11 @@ struct WP11_Object { typedef struct WP11_Find { int state; /* Whether operation is initialized */ - CK_OBJECT_HANDLE found[WP11_FIND_MAX]; + CK_OBJECT_HANDLE* found; /* List of object handles found */ int count; /* Count of object handles */ int curr; /* Index of last object returned */ + int capacity; /* Allocated entries in found */ } WP11_Find; #ifndef NO_RSA @@ -374,13 +375,21 @@ typedef struct WP11_CbcParams { Aes aes; /* AES object from wolfCrypt */ unsigned char partial[AES_BLOCK_SIZE]; /* Partial block when streaming */ + unsigned char final[AES_BLOCK_SIZE]; + /* Decrypted final block for retry */ byte partialSz; /* Size of partial block data */ + byte finalReady; /* Final block has been decrypted */ } WP11_CbcParams; #endif #ifdef HAVE_AESCTR typedef struct WP11_CtrParams { Aes aes; /* AES object from wolfCrypt */ + unsigned char counter[AES_BLOCK_SIZE]; + /* Next counter block to use */ + byte counterBits; /* Bits in counter field */ + byte offset; /* Bytes used in current stream block */ + byte exhausted; /* Counter field has wrapped */ } WP11_CtrParams; #endif @@ -6486,6 +6495,9 @@ static int wp11_Object_Load(WP11_Object* object, int tokenId, int objId) #endif #ifndef NO_AES case CKK_AES: + #endif + #ifdef WOLFPKCS11_HKDF + case CKK_HKDF: #endif case CKK_GENERIC_SECRET: ret = wp11_Object_Load_SymmKey(object, tokenId, objId); @@ -6617,14 +6629,6 @@ static int wp11_Object_Store(WP11_Object* object, int tokenId, int objId) /* Open access to key object. */ ret = wp11_Object_Store_Object(object, tokenId, objId); - if (ret == 0 && object->keyData == NULL && - (object->objClass == CKO_PRIVATE_KEY || - object->type == CKK_AES || - object->type == CKK_GENERIC_SECRET)) { - /* Generate new IV if needed */ - ret = wc_RNG_GenerateBlock(&object->slot->token.rng, object->iv, - sizeof(object->iv)); - } if (ret == 0) { if (object->objClass == CKO_CERTIFICATE) { ret = wp11_Object_Store_Cert(object, tokenId, objId); @@ -6678,6 +6682,9 @@ static int wp11_Object_Store(WP11_Object* object, int tokenId, int objId) #endif #ifndef NO_AES case CKK_AES: + #endif + #ifdef WOLFPKCS11_HKDF + case CKK_HKDF: #endif case CKK_GENERIC_SECRET: ret = wp11_Object_Store_SymmKey(object, tokenId, objId); @@ -6759,6 +6766,9 @@ static int wp11_Object_Decode(WP11_Object* object) #endif #ifndef NO_AES case CKK_AES: + #endif + #ifdef WOLFPKCS11_HKDF + case CKK_HKDF: #endif case CKK_GENERIC_SECRET: ret = wp11_Object_Decode_SymmKey(object); @@ -6783,7 +6793,7 @@ static int wp11_Object_Decode(WP11_Object* object) * @return 0 on success. * @return -ve on failure. */ -static int wp11_Object_Encode(WP11_Object* object, int protect) +static int wp11_Object_EncodeData(WP11_Object* object, int protect) { int ret; @@ -6857,6 +6867,9 @@ static int wp11_Object_Encode(WP11_Object* object, int protect) #endif #ifndef NO_AES case CKK_AES: + #endif + #ifdef WOLFPKCS11_HKDF + case CKK_HKDF: #endif case CKK_GENERIC_SECRET: ret = wp11_Object_Encode_SymmKey(object); @@ -6873,6 +6886,32 @@ static int wp11_Object_Encode(WP11_Object* object, int protect) return ret; } +static int wp11_Object_Encode(WP11_Object* object, int protect) +{ + int ret = 0; + int encrypt = object->objClass == CKO_PRIVATE_KEY || + object->type == CKK_AES || + object->type == CKK_GENERIC_SECRET; + +#ifdef WOLFPKCS11_HKDF + encrypt = encrypt || object->type == CKK_HKDF; +#endif + + /* Every AES-GCM encryption under the token key needs a fresh nonce. Do + * this immediately before encoding, while the plaintext is still the + * source of the ciphertext that will be persisted. */ + if (encrypt) { + WP11_Lock_LockRW(&object->slot->token.rngLock); + ret = wc_RNG_GenerateBlock(&object->slot->token.rng, object->iv, + sizeof(object->iv)); + WP11_Lock_UnlockRW(&object->slot->token.rngLock); + } + if (ret == 0) + ret = wp11_Object_EncodeData(object, protect); + + return ret; +} + /** * Unstore a key object to storage. * @@ -6965,6 +7004,9 @@ static int wp11_Object_Unstore(WP11_Object* object, int tokenId, int objId) #endif #ifndef NO_AES case CKK_AES: + #endif + #ifdef WOLFPKCS11_HKDF + case CKK_HKDF: #endif case CKK_GENERIC_SECRET: storeObjType = WOLFPKCS11_STORE_SYMMKEY; @@ -8697,6 +8739,30 @@ int WP11_Slot_IsLoggedIn(WP11_Slot* slot) state != WP11_APP_STATE_RW_PUBLIC); } +static int wp11_LoginStateIsUser(int state) +{ + return (state == WP11_APP_STATE_RO_USER || + state == WP11_APP_STATE_RW_USER); +} + +/** + * Check whether the normal user is logged in to the token. + * + * @param slot [in] Slot object referencing token. + * @return 1 when the normal user is logged in. + * 0 when the session is public or the SO is logged in. + */ +int WP11_Slot_IsUserLoggedIn(WP11_Slot* slot) +{ + int state; + + WP11_Lock_LockRO(&slot->lock); + state = slot->token.loginState; + WP11_Lock_UnlockRO(&slot->lock); + + return wp11_LoginStateIsUser(state); +} + void WP11_Slot_Logout(WP11_Slot* slot) { #ifndef WOLFPKCS11_NO_STORE @@ -9555,7 +9621,9 @@ int WP11_Session_SetCbcParams(WP11_Session* session, unsigned char* iv, * state here. Reset it before use (as the other Set*Params routines do) so * a fresh CBC operation cannot inherit a bogus partial-block count. */ cbc->partialSz = 0; + cbc->finalReady = 0; XMEMSET(cbc->partial, 0, sizeof(cbc->partial)); + XMEMSET(cbc->final, 0, sizeof(cbc->final)); /* AES object on session. */ ret = wc_AesInit(&cbc->aes, NULL, object->devId); @@ -9602,6 +9670,7 @@ int WP11_Session_SetCtrParams(WP11_Session* session, CK_ULONG ulCounterBits, if (ulCounterBits > 128 || ulCounterBits == 0) return BAD_FUNC_ARG; + XMEMSET(ctr, 0, sizeof(*ctr)); ret = wc_AesInit(&ctr->aes, NULL, object->devId); if (ret == 0) { if (object->onToken) @@ -9611,6 +9680,10 @@ int WP11_Session_SetCtrParams(WP11_Session* session, CK_ULONG ulCounterBits, if (object->onToken) WP11_Lock_UnlockRO(object->lock); } + if (ret == 0) { + XMEMCPY(ctr->counter, cb, sizeof(ctr->counter)); + ctr->counterBits = (byte)ulCounterBits; + } return ret; } @@ -9813,6 +9886,12 @@ int WP11_Session_AddObject(WP11_Session* session, int onToken, token = &session->slot->token; WP11_Lock_LockRW(&token->lock); if (onToken) { +#ifndef WOLFPKCS11_NO_STORE + WP11_Object* oldHead = token->object; + int oldObjCnt = token->objCnt; + int oldNextObjId = token->nextObjId; +#endif + if (token->objCnt >= WP11_TOKEN_OBJECT_CNT_MAX) ret = OBJ_COUNT_E; #ifndef WOLFPKCS11_NO_STORE @@ -9832,6 +9911,14 @@ int WP11_Session_AddObject(WP11_Session* session, int onToken, #ifndef WOLFPKCS11_NO_STORE if (ret == 0) { ret = wp11_Slot_Store(session->slot, (int)session->slotId); + if (ret != 0) { + token->object = oldHead; + token->objCnt = oldObjCnt; + token->nextObjId = oldNextObjId; + object->handle = CK_INVALID_HANDLE; + object->next = NULL; + object->lock = NULL; + } } #endif } @@ -10128,10 +10215,18 @@ int WP11_Session_FindInit(WP11_Session* session) if (session->find.state != WP11_FIND_STATE_NULL) ret = BAD_STATE_E; + if (ret == 0) { + session->find.found = (CK_OBJECT_HANDLE*)XMALLOC( + WP11_FIND_MAX * sizeof(*session->find.found), NULL, + DYNAMIC_TYPE_TMP_BUFFER); + if (session->find.found == NULL) + ret = MEMORY_E; + } if (ret == 0) { session->find.state = WP11_FIND_STATE_INIT; session->find.count = 0; session->find.curr = 0; + session->find.capacity = WP11_FIND_MAX; } return ret; @@ -10186,8 +10281,8 @@ static WP11_Object* wp11_Session_FindNext(WP11_Session* session, int onToken, if ((ret->opFlag & WP11_FLAG_PRIVATE) == WP11_FLAG_PRIVATE) { if (!onToken) WP11_Lock_LockRO(&session->slot->token.lock); - if (session->slot->token.loginState == WP11_APP_STATE_RW_PUBLIC || - session->slot->token.loginState == WP11_APP_STATE_RO_PUBLIC) { + if (!wp11_LoginStateIsUser( + session->slot->token.loginState)) { object = ret; ret = NULL; } @@ -10205,16 +10300,27 @@ static WP11_Object* wp11_Session_FindNext(WP11_Session* session, int onToken, * * @param session [in] Session object. * @param object [in] Object object to store reference to. - * @return FIND_FULL_E when the found list is full. + * @return MEMORY_E when the found list cannot be grown. * 0 on success. */ static int wp11_Session_FindMatched(WP11_Session* session, WP11_Object* object) { int ret = 0; - if (session->find.count == WP11_FIND_MAX) - ret = FIND_FULL_E; - else { + if (session->find.count == session->find.capacity) { + int capacity = session->find.capacity * 2; + CK_OBJECT_HANDLE* found = (CK_OBJECT_HANDLE*)XREALLOC( + session->find.found, capacity * sizeof(*found), NULL, + DYNAMIC_TYPE_TMP_BUFFER); + + if (found == NULL) + ret = MEMORY_E; + else { + session->find.found = found; + session->find.capacity = capacity; + } + } + if (ret == 0) { session->find.found[session->find.count++] = object->handle; session->find.state = WP11_FIND_STATE_FOUND; } @@ -10230,16 +10336,18 @@ static int wp11_Session_FindMatched(WP11_Session* session, WP11_Object* object) * @param pTemplate [in] Array of attributes that must match. * @param ulCount [in] Number of attributes in array. */ -void WP11_Session_Find(WP11_Session* session, int onToken, - CK_ATTRIBUTE_PTR pTemplate, CK_ULONG ulCount) +int WP11_Session_Find(WP11_Session* session, int onToken, + CK_ATTRIBUTE_PTR pTemplate, CK_ULONG ulCount) { WP11_Object* obj = NULL; + int ret = 0; int i; CK_ATTRIBUTE* attr; if (onToken) WP11_Lock_LockRO(&session->slot->token.lock); - while ((obj = wp11_Session_FindNext(session, onToken, obj)) != NULL) { + while (ret == 0 && + (obj = wp11_Session_FindNext(session, onToken, obj)) != NULL) { for (i = 0; i < (int)ulCount; i++) { attr = &pTemplate[i]; if (!WP11_Object_MatchAttr(obj, attr->type, (byte*)attr->pValue, @@ -10248,13 +10356,13 @@ void WP11_Session_Find(WP11_Session* session, int onToken, } } - if (i == (int)ulCount) { - if (wp11_Session_FindMatched(session, obj) == FIND_FULL_E) - break; - } + if (i == (int)ulCount) + ret = wp11_Session_FindMatched(session, obj); } if (onToken) WP11_Lock_UnlockRO(&session->slot->token.lock); + + return ret; } /** @@ -10292,6 +10400,13 @@ int WP11_Session_FindGet(WP11_Session* session, CK_OBJECT_HANDLE* handle) */ void WP11_Session_FindFinal(WP11_Session* session) { + if (session->find.found != NULL) { + XFREE(session->find.found, NULL, DYNAMIC_TYPE_TMP_BUFFER); + session->find.found = NULL; + } + session->find.count = 0; + session->find.curr = 0; + session->find.capacity = 0; session->find.state = WP11_FIND_STATE_NULL; } @@ -11468,10 +11583,10 @@ int WP11_Object_Find(WP11_Session* session, CK_OBJECT_HANDLE objHandle, int loginState; WP11_Lock_LockRO(&session->slot->lock); loginState = session->slot->token.loginState; - /* F-3835: resolving a CKA_PRIVATE object by handle from a public - * session must be denied even when the user PIN is empty. */ - if (loginState == WP11_APP_STATE_RW_PUBLIC || - loginState == WP11_APP_STATE_RO_PUBLIC) { + /* F-3835: resolving a CKA_PRIVATE object requires a normal-user + * login even when the user PIN is empty. An SO login grants no + * access to private objects. */ + if (!wp11_LoginStateIsUser(loginState)) { ret = BAD_FUNC_ARG; } WP11_Lock_UnlockRO(&session->slot->lock); @@ -16106,12 +16221,14 @@ int WP11_AesCbcPad_DecryptFinal(unsigned char* dec, word32* decSz, unsigned char* p = dec; size_t mask; - ret = wc_AesCbcDecrypt(&cbc->aes, cbc->partial, cbc->partial, - cbc->partialSz); - if (ret == 0) { + if (!cbc->finalReady) { + ret = wc_AesCbcDecrypt(&cbc->aes, cbc->final, cbc->partial, + cbc->partialSz); + } + if (ret == 0 && !cbc->finalReady) { byte padBad; - padCnt = cbc->partial[AES_BLOCK_SIZE-1]; + padCnt = cbc->final[AES_BLOCK_SIZE-1]; /* Validate PKCS#7 padding in constant time: * padCnt must be 1..AES_BLOCK_SIZE and all padding bytes must equal @@ -16122,13 +16239,17 @@ int WP11_AesCbcPad_DecryptFinal(unsigned char* dec, word32* decSz, /* inPad is 0xFF when i is in the padding region, 0x00 otherwise */ byte inPad = (byte)(0 - ((unsigned)(AES_BLOCK_SIZE - 1 - i) < (unsigned)padCnt)); - padBad |= inPad & (cbc->partial[i] ^ padCnt); + padBad |= inPad & (cbc->final[i] ^ padCnt); } if (padBad) { ret = BAD_PADDING_E; } + else { + cbc->finalReady = 1; + } } if (ret == 0) { + padCnt = cbc->final[AES_BLOCK_SIZE-1]; outSz = AES_BLOCK_SIZE - (padCnt & (0 - (padCnt <= AES_BLOCK_SIZE))); /* Refuse to overflow caller's buffer. Output size is 0..15 bytes; * caller passes the remaining capacity in *decSz. On a too-small @@ -16144,14 +16265,16 @@ int WP11_AesCbcPad_DecryptFinal(unsigned char* dec, word32* decSz, mask = (size_t)0 - (i != outSz); p = (unsigned char*)((size_t)p & mask); p = (unsigned char*)((size_t)p | ((size_t)tmp & (~mask))); - *p = cbc->partial[i]; + *p = cbc->final[i]; p++; } *decSz = outSz; } wc_AesFree(&cbc->aes); + wc_ForceZero(cbc->final, sizeof(cbc->final)); cbc->partialSz = 0; + cbc->finalReady = 0; session->init = 0; return ret; @@ -16159,6 +16282,60 @@ int WP11_AesCbcPad_DecryptFinal(unsigned char* dec, word32* decSz, #endif /* HAVE_AES_CBC */ #ifdef HAVE_AESCTR +/* Add to the least-significant counterBits bits of a big-endian counter. */ +static int wp11_AesCtr_Add(unsigned char* counter, byte counterBits, + word32 add) +{ + int first = AES_BLOCK_SIZE - (counterBits + 7) / 8; + int i; + word32 carry = add; + byte mask = (counterBits & 7) == 0 ? 0xff : + (byte)((1U << (counterBits & 7)) - 1U); + + for (i = AES_BLOCK_SIZE - 1; i >= first; i--) { + word32 value = counter[i]; + word32 sum; + + if (i == first) + value &= mask; + sum = value + (carry & 0xff); + carry = (carry >> 8) + (sum >> 8); + if (i == first) { + counter[i] = (counter[i] & (byte)~mask) | (byte)(sum & mask); + if (sum > mask) + carry = 1; + } + else { + counter[i] = (byte)sum; + } + } + + return carry != 0; +} + +/* Check that all counters needed for an update are still in range. */ +static int wp11_AesCtr_Check(WP11_CtrParams* ctr, word32 inSz, + word32* newBlocks) +{ + unsigned char counter[AES_BLOCK_SIZE]; + word32 available = ctr->offset == 0 ? 0 : AES_BLOCK_SIZE - ctr->offset; + word32 remaining = inSz > available ? inSz - available : 0; + + *newBlocks = remaining / AES_BLOCK_SIZE; + if ((remaining & (AES_BLOCK_SIZE - 1)) != 0) + (*newBlocks)++; + if (*newBlocks == 0) + return 0; + if (ctr->exhausted) + return WP11_CTR_OVERFLOW_E; + + XMEMCPY(counter, ctr->counter, sizeof(counter)); + if (wp11_AesCtr_Add(counter, ctr->counterBits, *newBlocks - 1)) + return WP11_CTR_OVERFLOW_E; + + return 0; +} + /** * Encrypt or decrypt data with AES-CTR. * Output buffer must be large enough to hold all data. @@ -16204,12 +16381,19 @@ int WP11_AesCtr_Update(unsigned char* in, word32 inSz, unsigned char* out, { int ret = 0; WP11_CtrParams* ctr = &session->params.ctr; + word32 newBlocks; if (*outSz < inSz) return BUFFER_E; - ret = wc_AesCtrEncrypt(&ctr->aes, out, in, inSz); + ret = wp11_AesCtr_Check(ctr, inSz, &newBlocks); if (ret == 0) + ret = wc_AesCtrEncrypt(&ctr->aes, out, in, inSz); + if (ret == 0) { + if (wp11_AesCtr_Add(ctr->counter, ctr->counterBits, newBlocks)) + ctr->exhausted = 1; + ctr->offset = (byte)((ctr->offset + inSz) & (AES_BLOCK_SIZE - 1)); *outSz = inSz; + } return ret; } @@ -17161,8 +17345,10 @@ int WP11_AesKeyWrapPad_Decrypt(unsigned char* enc, word32 encSz, wc_ForceZero(padBuf, paddedSz); XFREE(padBuf, NULL, DYNAMIC_TYPE_TMP_BUFFER); - wc_AesFree(&wrap->aes); - session->init = 0; + if (ret != BUFFER_E) { + wc_AesFree(&wrap->aes); + session->init = 0; + } return ret; } #endif /* HAVE_AES_KEYWRAP */ diff --git a/src/slot.c b/src/slot.c index a987cc30..5f7a21bb 100644 --- a/src/slot.c +++ b/src/slot.c @@ -474,7 +474,7 @@ static CK_MECHANISM_TYPE mechanismList[] = { #endif #endif #ifdef WOLFPKCS11_NSS - /* Only advertise CKM_SSL3_MASTER_KEY_DERIVE. Not implemented. */ + /* NSS uses this as a target-key marker when unwrapping TLS secrets. */ CKM_SSL3_MASTER_KEY_DERIVE, CKM_NSS_PKCS12_PBE_SHA224_HMAC_KEY_GEN, CKM_NSS_PKCS12_PBE_SHA256_HMAC_KEY_GEN, @@ -724,13 +724,14 @@ static CK_MECHANISM_INFO nssPkcs12PbeSha384HmacKeyGenMechInfo = { static CK_MECHANISM_INFO nssPkcs12PbeSha512HmacKeyGenMechInfo = { 512, 512, CKF_GENERATE }; -#endif -#endif -#ifdef WOLFPKCS11_NSS -static CK_MECHANISM_INFO ssl3MasterKeyDeriveInfo = { - 48, 48, CKF_DERIVE +/* NSS requires this mechanism identifier when selecting the slot used to + * unwrap cached TLS secrets. C_DeriveKey does not implement the mechanism, so + * do not advertise CKF_DERIVE. */ +static CK_MECHANISM_INFO ssl3MasterKeyTargetInfo = { + 48, 48, 0 }; #endif +#endif #ifdef WOLFSSL_HAVE_PRF static CK_MECHANISM_INFO tlsMacMechInfo = { 0, 512, CKF_SIGN | CKF_VERIFY @@ -1236,15 +1237,12 @@ CK_RV C_GetMechanismInfo(CK_SLOT_ID slotID, CK_MECHANISM_TYPE type, XMEMCPY(pInfo, &nssPkcs12PbeSha512HmacKeyGenMechInfo, sizeof(CK_MECHANISM_INFO)); break; -#endif -#endif -#ifdef WOLFPKCS11_NSS - /* Only advertise CKM_SSL3_MASTER_KEY_DERIVE. Not implemented. */ case CKM_SSL3_MASTER_KEY_DERIVE: - XMEMCPY(pInfo, &ssl3MasterKeyDeriveInfo, + XMEMCPY(pInfo, &ssl3MasterKeyTargetInfo, sizeof(CK_MECHANISM_INFO)); break; #endif +#endif #ifdef WOLFSSL_HAVE_PRF case CKM_TLS_MAC: XMEMCPY(pInfo, &tlsMacMechInfo, diff --git a/src/wolfpkcs11.c b/src/wolfpkcs11.c index 28fd3bb0..90a44720 100644 --- a/src/wolfpkcs11.c +++ b/src/wolfpkcs11.c @@ -470,38 +470,38 @@ CK_RV C_GetInterface(CK_UTF8CHAR_PTR pInterfaceName, CK_VERSION_PTR pVersion, { int i; - if (ppInterface == NULL) { - return CKR_ARGUMENTS_BAD; - } - - if (pInterfaceName == NULL_PTR) { - /* return default interface */ - *ppInterface = &interfaces[DEFAULT_INTERFACE]; - return CKR_OK; - } + if (ppInterface == NULL) + return CKR_ARGUMENTS_BAD; - for (i = 0; i < NUM_INTERFACES; i++) { - CK_VERSION_PTR interface_version = (CK_VERSION_PTR)interfaces[i].pFunctionList; + for (i = 0; i < NUM_INTERFACES; i++) { + CK_VERSION_PTR interfaceVersion = + (CK_VERSION_PTR)interfaces[i].pFunctionList; - if (strcmp((char*)pInterfaceName, (char*)interfaces[i].pInterfaceName) != 0) - continue; + if (pInterfaceName == NULL_PTR && i != DEFAULT_INTERFACE) + continue; + if (pInterfaceName != NULL_PTR && + strcmp((char*)pInterfaceName, + (char*)interfaces[i].pInterfaceName) != 0) { + continue; + } - /* If version is not null, it must match */ - if (pVersion != NULL_PTR && (pVersion->major != interface_version->major || - pVersion->minor != interface_version->minor)) { - continue; - } + /* If version is not null, it must match. */ + if (pVersion != NULL_PTR && + (pVersion->major != interfaceVersion->major || + pVersion->minor != interfaceVersion->minor)) { + continue; + } - /* If any flags specified, it must be supported by the interface */ - if ((flags & interfaces[i].flags) != flags) - continue; + /* If any flags are specified, the interface must support them. */ + if ((flags & interfaces[i].flags) != flags) + continue; - *ppInterface = &interfaces[i]; + *ppInterface = &interfaces[i]; return CKR_OK; - } + } - return CKR_ARGUMENTS_BAD; + return CKR_ARGUMENTS_BAD; } #endif /* defined WOLFPKCS11_PKCS11_V3_0 */ diff --git a/tests/aes_ctr_counterbits_test.c b/tests/aes_ctr_counterbits_test.c index df357d1d..0c7db5a7 100644 --- a/tests/aes_ctr_counterbits_test.c +++ b/tests/aes_ctr_counterbits_test.c @@ -19,8 +19,8 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA * * Regression test for issue F-5523: AES-CTR setup must reject CK_AES_CTR_PARAMS - * with ulCounterBits == 0 or > 128 (CKR_MECHANISM_PARAM_INVALID) in both - * C_EncryptInit and C_DecryptInit, while accepting the valid value 128. + * with ulCounterBits == 0 or > 128 and enforce the selected counter field + * without allowing it to overflow into nonce bits. */ #ifdef HAVE_CONFIG_H @@ -306,6 +306,65 @@ static int test_ctr_decrypt_init(CK_SESSION_HANDLE session, return result; } +/* A one-bit counter permits exactly two blocks when initialized to zero. + * Exercise single-part preflight and a multipart operation that consumes a + * block across update boundaries before exhausting the counter field. */ +static int test_ctr_overflow(CK_SESSION_HANDLE session, CK_OBJECT_HANDLE key) +{ + CK_RV ret; + CK_MECHANISM mech; + CK_AES_CTR_PARAMS ctrParams; + byte in[33]; + byte out[33]; + CK_ULONG outSz; + int i; + int result = 0; + + XMEMSET(in, 0x5a, sizeof(in)); + + ctr_mech_init(&mech, &ctrParams, 1); + ret = funcList->C_EncryptInit(session, &mech, key); + CHECK_CKR(ret, "C_EncryptInit ulCounterBits=1", CKR_OK); + + XMEMSET(out, 0xa5, sizeof(out)); + outSz = sizeof(out); + ret = funcList->C_Encrypt(session, in, sizeof(in), out, &outSz); + CHECK_CKR(ret, "C_Encrypt rejects one-bit counter overflow", + CKR_DATA_LEN_RANGE); + for (i = 0; i < (int)sizeof(out) && out[i] == 0xa5; i++) { + } + CHECK_CKR(i == (int)sizeof(out) ? CKR_OK : CKR_GENERAL_ERROR, + "C_Encrypt overflow writes no output", CKR_OK); + + ctr_mech_init(&mech, &ctrParams, 1); + ret = funcList->C_EncryptInit(session, &mech, key); + CHECK_CKR(ret, "C_EncryptInit multipart ulCounterBits=1", CKR_OK); + + outSz = 15; + ret = funcList->C_EncryptUpdate(session, in, 15, out, &outSz); + CHECK_CKR(ret, "C_EncryptUpdate consumes partial first counter", CKR_OK); + outSz = 17; + ret = funcList->C_EncryptUpdate(session, in + 15, 17, out + 15, &outSz); + CHECK_CKR(ret, "C_EncryptUpdate consumes last counter", CKR_OK); + outSz = 1; + ret = funcList->C_EncryptUpdate(session, in + 32, 1, out + 32, &outSz); + CHECK_CKR(ret, "C_EncryptUpdate rejects exhausted counter", + CKR_DATA_LEN_RANGE); + + /* Starting at the maximum one-bit value permits only one block. */ + ctr_mech_init(&mech, &ctrParams, 1); + ctrParams.cb[15] = 1; + ret = funcList->C_DecryptInit(session, &mech, key); + CHECK_CKR(ret, "C_DecryptInit maximum one-bit counter", CKR_OK); + outSz = 17; + ret = funcList->C_Decrypt(session, in, 17, out, &outSz); + CHECK_CKR(ret, "C_Decrypt rejects maximum counter overflow", + CKR_DATA_LEN_RANGE); + +cleanup: + return result; +} + static int run_aes_ctr_counterbits_test(void) { CK_RV ret; @@ -393,6 +452,8 @@ static int run_aes_ctr_counterbits_test(void) result = -1; if (test_ctr_decrypt_init(session, key) != 0) result = -1; + if (test_ctr_overflow(session, key) != 0) + result = -1; pkcs11_close_session(session); pkcs11_final(); diff --git a/tests/aes_keywrap_pad_test.c b/tests/aes_keywrap_pad_test.c index 7eb77302..7c5ca586 100644 --- a/tests/aes_keywrap_pad_test.c +++ b/tests/aes_keywrap_pad_test.c @@ -488,6 +488,7 @@ static int test_unwrap_buffer_size(CK_SESSION_HANDLE session) { CK_RV ret; CK_OBJECT_HANDLE kek = CK_INVALID_HANDLE; + CK_MECHANISM mech = { CKM_AES_KEY_WRAP_PAD, NULL, 0 }; byte wrapped[64]; byte out[64]; CK_ULONG wrappedLen, outLen; @@ -503,17 +504,21 @@ static int test_unwrap_buffer_size(CK_SESSION_HANDLE session) CHECK_CKR(ret, "Bufsize: wrap", CKR_OK); CHECK_COND(wrappedLen == sizeof(rfc5649_ct1), "Bufsize: wrapped length"); - /* Buffer smaller than the recovered plaintext: reject and report size. */ + /* Buffer smaller than the recovered plaintext: reject and report size, + * leaving the operation active for a retry. */ + ret = funcList->C_DecryptInit(session, &mech, kek); + CHECK_CKR(ret, "Bufsize: decrypt init", CKR_OK); outLen = sizeof(rfc5649_pt1) - 4; - ret = kwp_unwrap(session, kek, wrapped, wrappedLen, out, &outLen); + ret = funcList->C_Decrypt(session, wrapped, wrappedLen, out, &outLen); CHECK_CKR(ret, "Bufsize: too-small buffer", CKR_BUFFER_TOO_SMALL); CHECK_COND(outLen == sizeof(rfc5649_pt1), "Bufsize: required length reported"); - /* Buffer equal to the recovered length but below the upper bound: succeed. */ + /* Retry without another C_DecryptInit. A buffer equal to the recovered + * length but below the upper bound must succeed. */ outLen = sizeof(rfc5649_pt1); - ret = kwp_unwrap(session, kek, wrapped, wrappedLen, out, &outLen); - CHECK_CKR(ret, "Bufsize: exact-size buffer", CKR_OK); + ret = funcList->C_Decrypt(session, wrapped, wrappedLen, out, &outLen); + CHECK_CKR(ret, "Bufsize: exact-size retry", CKR_OK); CHECK_COND(outLen == sizeof(rfc5649_pt1) && XMEMCMP(out, rfc5649_pt1, sizeof(rfc5649_pt1)) == 0, "Bufsize: exact-size buffer recovers plaintext"); diff --git a/tests/copyobject_token_test.c b/tests/copyobject_token_test.c index bf26bfca..faa18307 100644 --- a/tests/copyobject_token_test.c +++ b/tests/copyobject_token_test.c @@ -32,6 +32,18 @@ #include +#if defined(_WIN32) || defined(_MSC_VER) + #include + #include + #include + #define TEST_SET_READONLY(path) _chmod(path, _S_IREAD) + #define TEST_SET_WRITABLE(path) _chmod(path, _S_IREAD | _S_IWRITE) +#else + #include + #define TEST_SET_READONLY(path) chmod(path, 0500) + #define TEST_SET_WRITABLE(path) chmod(path, 0700) +#endif + #ifndef WOLFSSL_USER_SETTINGS #include #endif @@ -448,6 +460,61 @@ static int test_copy_token_findable(CK_SESSION_HANDLE session) return result; } +/* A failed persistence attempt must roll the new token object out of the + * in-memory list before C_CopyObject frees it. */ +#ifndef WOLFPKCS11_NO_STORE +static int test_copy_token_store_failure(CK_SESSION_HANDLE session) +{ + CK_RV ret; + CK_OBJECT_HANDLE src = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE copy = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE found; + CK_ULONG foundCount = 0; + CK_ATTRIBUTE copyTmpl[] = { + { CKA_TOKEN, &ckTrue, sizeof(ckTrue) }, + }; + CK_ATTRIBUTE findTmpl[] = { + { CKA_TOKEN, &ckTrue, sizeof(ckTrue) }, + }; + int storeReadOnly = 0; + int result = 0; + + ret = create_aes_key(session, CK_FALSE, &src); + CHECK_CKR(ret, "Test6: create session source object", CKR_OK); + + ret = TEST_SET_READONLY(COPY_TOKEN_TEST_DIR); + CHECK_COND(ret == 0, "Test6: make token storage read-only"); + storeReadOnly = 1; + + ret = funcList->C_CopyObject(session, src, copyTmpl, 1, ©); + (void)TEST_SET_WRITABLE(COPY_TOKEN_TEST_DIR); + storeReadOnly = 0; + CHECK_CKR(ret, "Test6: token copy reports persistence failure", + CKR_FUNCTION_FAILED); + + ret = funcList->C_FindObjectsInit(session, findTmpl, 1); + CHECK_CKR(ret, "Test6: find token objects after failed copy", CKR_OK); + ret = funcList->C_FindObjects(session, &found, 1, &foundCount); + CHECK_CKR(ret, "Test6: read token objects after failed copy", CKR_OK); + ret = funcList->C_FindObjectsFinal(session); + CHECK_CKR(ret, "Test6: finalize find after failed copy", CKR_OK); + CHECK_COND(foundCount == 0, + "Test6: failed copy was rolled out of token list"); + + ret = funcList->C_CopyObject(session, src, copyTmpl, 1, ©); + CHECK_CKR(ret, "Test6: later token copy succeeds", CKR_OK); + +cleanup: + if (storeReadOnly) + (void)TEST_SET_WRITABLE(COPY_TOKEN_TEST_DIR); + if (copy != CK_INVALID_HANDLE) + funcList->C_DestroyObject(session, copy); + if (src != CK_INVALID_HANDLE) + funcList->C_DestroyObject(session, src); + return result; +} +#endif + static int copyobject_token_test(void) { CK_RV ret; @@ -500,6 +567,10 @@ static int copyobject_token_test(void) result = -1; if (test_copy_token_findable(session) != 0) result = -1; +#ifndef WOLFPKCS11_NO_STORE + if (test_copy_token_store_failure(session) != 0) + result = -1; +#endif pkcs11_close_session(session); pkcs11_final(); diff --git a/tests/decrypt_final_bufsize_test.c b/tests/decrypt_final_bufsize_test.c index f02a3018..19006e14 100644 --- a/tests/decrypt_final_bufsize_test.c +++ b/tests/decrypt_final_bufsize_test.c @@ -165,6 +165,16 @@ static int run_test(void) CHECK_TRUE(i == (int)sizeof(lastPart), "no write past the caller-declared buffer length"); + /* Retry without reinitializing. The decrypted final block must have been + * retained without decrypting it a second time or advancing CBC again. */ + XMEMSET(lastPart, 0, sizeof(lastPart)); + lastPartLen = sizeof(lastPart); + rv = funcList->C_DecryptFinal(session, lastPart, &lastPartLen); + CHECK_RV(rv, "C_DecryptFinal(retry)", CKR_OK); + CHECK_TRUE(lastPartLen == sizeof(plain) && + XMEMCMP(lastPart, plain, sizeof(plain)) == 0, + "undersized-buffer retry recovers plaintext"); + out: if (session != 0) funcList->C_CloseSession(session); diff --git a/tests/hkdf_persistence_test.c b/tests/hkdf_persistence_test.c new file mode 100644 index 00000000..3acabc64 --- /dev/null +++ b/tests/hkdf_persistence_test.c @@ -0,0 +1,336 @@ +/* hkdf_persistence_test.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfPKCS11. + * + * Regression test for F-8635: CKK_HKDF token keys must use the symmetric-key + * persistence path and survive C_Finalize/C_Initialize. + */ + +#ifdef HAVE_CONFIG_H + #include +#endif + +#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" +#include "pkcs11_test_util.h" + +#if defined(WOLFPKCS11_HKDF) && !defined(WOLFPKCS11_NO_STORE) + +#define TEST_DIR "." PATH_SEP "store" PATH_SEP "hkdf_persistence_test" +#define TOKEN_FILE TEST_DIR PATH_SEP "wp11_token_0000000000000001" +#define OBJECT_FILE_0 \ + TEST_DIR PATH_SEP "wp11_obj_0000000000000001_0000000000000000" +#define OBJECT_FILE_1 \ + TEST_DIR PATH_SEP "wp11_obj_0000000000000001_0000000000000001" +#define KEY_FILE_0 \ + TEST_DIR PATH_SEP "wp11_symmkey_0000000000000001_0000000000000000" +#define KEY_FILE_1 \ + TEST_DIR PATH_SEP "wp11_symmkey_0000000000000001_0000000000000001" + +static byte soPin[] = "password123456"; +static byte userPin[] = "wolfpkcs11-test"; +static byte keyId[] = "hkdf-persistence"; + +static void cleanup_test_files(void) +{ + (void)remove(KEY_FILE_1); + (void)remove(KEY_FILE_0); + (void)remove(OBJECT_FILE_1); + (void)remove(OBJECT_FILE_0); + (void)remove(TOKEN_FILE); +} + +static CK_RV initialize(CK_SLOT_ID* slot) +{ + CK_RV rv; + CK_C_INITIALIZE_ARGS args; + CK_SLOT_ID slots[16]; + CK_ULONG count = sizeof(slots) / sizeof(*slots); + + XMEMSET(&args, 0, sizeof(args)); + args.flags = CKF_OS_LOCKING_OK; + rv = funcList->C_Initialize(&args); + if (rv == CKR_OK) + rv = funcList->C_GetSlotList(CK_TRUE, slots, &count); + if (rv == CKR_OK && count == 0) + rv = CKR_TOKEN_NOT_PRESENT; + if (rv == CKR_OK) + *slot = slots[0]; + + return rv; +} + +static CK_RV provision_token(CK_SLOT_ID slot) +{ + CK_RV rv; + CK_SESSION_HANDLE session = CK_INVALID_HANDLE; + CK_UTF8CHAR label[32]; + CK_FLAGS flags = CKF_SERIAL_SESSION | CKF_RW_SESSION; + + XMEMSET(label, ' ', sizeof(label)); + XMEMCPY(label, "wolfpkcs11", 10); + rv = funcList->C_InitToken(slot, soPin, sizeof(soPin) - 1, label); + if (rv == CKR_OK) + rv = funcList->C_OpenSession(slot, flags, NULL, NULL, &session); + if (rv == CKR_OK) + rv = funcList->C_Login(session, CKU_SO, soPin, sizeof(soPin) - 1); + if (rv == CKR_OK) + rv = funcList->C_InitPIN(session, userPin, sizeof(userPin) - 1); + + if (session != CK_INVALID_HANDLE) { + (void)funcList->C_Logout(session); + (void)funcList->C_CloseSession(session); + } + return rv; +} + +static CK_RV open_user_session(CK_SLOT_ID slot, CK_SESSION_HANDLE* session) +{ + CK_RV rv; + CK_FLAGS flags = CKF_SERIAL_SESSION | CKF_RW_SESSION; + + rv = funcList->C_OpenSession(slot, flags, NULL, NULL, session); + if (rv == CKR_OK) { + rv = funcList->C_Login(*session, CKU_USER, userPin, + sizeof(userPin) - 1); + } + return rv; +} + +static CK_RV generate_token_key(CK_SESSION_HANDLE session) +{ + CK_MECHANISM mech = { CKM_HKDF_KEY_GEN, NULL, 0 }; + CK_OBJECT_HANDLE key = CK_INVALID_HANDLE; + CK_ULONG valueLen = 32; + CK_BBOOL ckTrue = CK_TRUE; + CK_BBOOL ckFalse = CK_FALSE; + CK_ATTRIBUTE tmpl[] = { + { CKA_TOKEN, &ckTrue, sizeof(ckTrue) }, + { CKA_PRIVATE, &ckTrue, sizeof(ckTrue) }, + { CKA_SENSITIVE, &ckFalse, sizeof(ckFalse) }, + { CKA_EXTRACTABLE, &ckTrue, sizeof(ckTrue) }, + { CKA_VALUE_LEN, &valueLen, sizeof(valueLen) }, + { CKA_ID, keyId, sizeof(keyId) - 1 }, + }; + + return funcList->C_GenerateKey(session, &mech, tmpl, + sizeof(tmpl) / sizeof(*tmpl), &key); +} + +static CK_RV read_file(const char* path, byte* data, size_t dataSz, + size_t* readSz) +{ + FILE* file = fopen(path, "rb"); + + if (file == NULL) + return CKR_GENERAL_ERROR; + *readSz = fread(data, 1, dataSz, file); + if (ferror(file)) { + fclose(file); + return CKR_GENERAL_ERROR; + } + fclose(file); + return CKR_OK; +} + +static CK_RV check_unique_persistent_nonces(CK_SESSION_HANDLE session) +{ + CK_RV rv; + CK_OBJECT_HANDLE keys[2] = { CK_INVALID_HANDLE, CK_INVALID_HANDLE }; + CK_OBJECT_CLASS objClass = CKO_SECRET_KEY; + CK_KEY_TYPE keyType = CKK_GENERIC_SECRET; + CK_BBOOL ckTrue = CK_TRUE; + CK_BBOOL ckFalse = CK_FALSE; + byte secret[32]; + byte ids[2] = { 1, 2 }; + byte objectData[2][12]; + byte keyData[2][64]; + byte zeroIv[12] = { 0 }; + size_t objectSz[2]; + size_t keySz[2]; + CK_ATTRIBUTE tmpl[] = { + { CKA_CLASS, &objClass, sizeof(objClass) }, + { CKA_KEY_TYPE, &keyType, sizeof(keyType) }, + { CKA_TOKEN, &ckTrue, sizeof(ckTrue) }, + { CKA_PRIVATE, &ckTrue, sizeof(ckTrue) }, + { CKA_SENSITIVE, &ckFalse, sizeof(ckFalse) }, + { CKA_EXTRACTABLE, &ckTrue, sizeof(ckTrue) }, + { CKA_VALUE, secret, sizeof(secret) }, + { CKA_ID, &ids[0], sizeof(ids[0]) }, + }; + + XMEMSET(secret, 0x3c, sizeof(secret)); + rv = funcList->C_CreateObject(session, tmpl, + sizeof(tmpl) / sizeof(*tmpl), &keys[0]); + if (rv == CKR_OK) { + tmpl[7].pValue = &ids[1]; + rv = funcList->C_CreateObject(session, tmpl, + sizeof(tmpl) / sizeof(*tmpl), &keys[1]); + } + if (rv == CKR_OK) + rv = read_file(OBJECT_FILE_0, objectData[0], sizeof(objectData[0]), + &objectSz[0]); + if (rv == CKR_OK) + rv = read_file(OBJECT_FILE_1, objectData[1], sizeof(objectData[1]), + &objectSz[1]); + if (rv == CKR_OK) + rv = read_file(KEY_FILE_0, keyData[0], sizeof(keyData[0]), &keySz[0]); + if (rv == CKR_OK) + rv = read_file(KEY_FILE_1, keyData[1], sizeof(keyData[1]), &keySz[1]); + if (rv == CKR_OK && + (objectSz[0] != sizeof(objectData[0]) || + objectSz[1] != sizeof(objectData[1]) || + XMEMCMP(objectData[0], zeroIv, sizeof(zeroIv)) == 0 || + XMEMCMP(objectData[1], zeroIv, sizeof(zeroIv)) == 0 || + XMEMCMP(objectData[0], objectData[1], sizeof(objectData[0])) == 0)) { + rv = CKR_GENERAL_ERROR; + } + if (rv == CKR_OK && + (keySz[0] != keySz[1] || + XMEMCMP(keyData[0], keyData[1], keySz[0]) == 0)) { + rv = CKR_GENERAL_ERROR; + } + + if (keys[1] != CK_INVALID_HANDLE) + (void)funcList->C_DestroyObject(session, keys[1]); + if (keys[0] != CK_INVALID_HANDLE) + (void)funcList->C_DestroyObject(session, keys[0]); + + return rv; +} + +static CK_RV find_and_check_token_key(CK_SESSION_HANDLE session) +{ + CK_RV rv; + CK_OBJECT_HANDLE key = CK_INVALID_HANDLE; + CK_OBJECT_CLASS objClass = CKO_SECRET_KEY; + CK_KEY_TYPE keyType = CKK_HKDF; + CK_ULONG count = 0; + CK_ULONG valueLen = 0; + byte value[32]; + CK_ATTRIBUTE findTmpl[] = { + { CKA_CLASS, &objClass, sizeof(objClass) }, + { CKA_KEY_TYPE, &keyType, sizeof(keyType) }, + { CKA_ID, keyId, sizeof(keyId) - 1 }, + }; + CK_ATTRIBUTE getTmpl[] = { + { CKA_KEY_TYPE, &keyType, sizeof(keyType) }, + { CKA_VALUE_LEN, &valueLen, sizeof(valueLen) }, + { CKA_VALUE, value, sizeof(value) }, + }; + + rv = funcList->C_FindObjectsInit(session, findTmpl, + sizeof(findTmpl) / sizeof(*findTmpl)); + if (rv == CKR_OK) + rv = funcList->C_FindObjects(session, &key, 1, &count); + if (rv == CKR_OK) + rv = funcList->C_FindObjectsFinal(session); + if (rv == CKR_OK && count != 1) + rv = CKR_GENERAL_ERROR; + if (rv == CKR_OK) + rv = funcList->C_GetAttributeValue(session, key, getTmpl, + sizeof(getTmpl) / sizeof(*getTmpl)); + if (rv == CKR_OK && + (keyType != CKK_HKDF || valueLen != sizeof(value))) { + rv = CKR_GENERAL_ERROR; + } + if (rv == CKR_OK) + rv = funcList->C_DestroyObject(session, key); + + return rv; +} + +static void run_test(void) +{ + CK_RV rv; + CK_SLOT_ID slot = 0; + CK_SESSION_HANDLE session = CK_INVALID_HANDLE; + + cleanup_test_files(); + + rv = pkcs11_load(); + CHECK_RV(rv, "load library (create phase)", CKR_OK); + if (rv != CKR_OK) + return; + rv = initialize(&slot); + CHECK_RV(rv, "initialize (create phase)", CKR_OK); + if (rv == CKR_OK) + rv = provision_token(slot); + CHECK_RV(rv, "provision token", CKR_OK); + if (rv == CKR_OK) + rv = open_user_session(slot, &session); + CHECK_RV(rv, "open user session (create phase)", CKR_OK); + if (rv == CKR_OK) + rv = check_unique_persistent_nonces(session); + CHECK_RV(rv, "persistent secrets use distinct IVs and ciphertexts", CKR_OK); + if (rv == CKR_OK) + rv = generate_token_key(session); + CHECK_RV(rv, "generate persistent CKK_HKDF key", CKR_OK); + if (session != CK_INVALID_HANDLE) + (void)funcList->C_CloseSession(session); + (void)funcList->C_Finalize(NULL); + pkcs11_unload(); + + session = CK_INVALID_HANDLE; + rv = pkcs11_load(); + CHECK_RV(rv, "load library (reload phase)", CKR_OK); + if (rv != CKR_OK) + return; + rv = initialize(&slot); + CHECK_RV(rv, "initialize (reload phase)", CKR_OK); + if (rv == CKR_OK) + rv = open_user_session(slot, &session); + CHECK_RV(rv, "open user session (reload phase)", CKR_OK); + if (rv == CKR_OK) + rv = find_and_check_token_key(session); + CHECK_RV(rv, "reload and read persistent CKK_HKDF key", CKR_OK); + + if (session != CK_INVALID_HANDLE) + (void)funcList->C_CloseSession(session); + (void)funcList->C_Finalize(NULL); + pkcs11_unload(); + cleanup_test_files(); +} + +int main(int argc, char* argv[]) +{ + (void)argc; + (void)argv; +#ifndef WOLFPKCS11_NO_ENV + XSETENV("WOLFPKCS11_TOKEN_PATH", TEST_DIR, 1); +#endif + + printf("=== wolfPKCS11 HKDF persistence test ===\n"); + run_test(); + return pkcs11_test_summary(); +} + +#else + +int main(int argc, char* argv[]) +{ + (void)argc; + (void)argv; + printf("HKDF persistence not available, skipping test\n"); + return 0; +} + +#endif diff --git a/tests/include.am b/tests/include.am index 5af7cbba..00706ee2 100644 --- a/tests/include.am +++ b/tests/include.am @@ -250,6 +250,11 @@ noinst_PROGRAMS += tests/hbs_persistence_test tests_hbs_persistence_test_SOURCES = tests/hbs_persistence_test.c tests_hbs_persistence_test_LDADD = +check_PROGRAMS += tests/hkdf_persistence_test +noinst_PROGRAMS += tests/hkdf_persistence_test +tests_hkdf_persistence_test_SOURCES = tests/hkdf_persistence_test.c +tests_hkdf_persistence_test_LDADD = + if BUILD_STATIC tests_pkcs11test_LDADD += src/libwolfpkcs11.la tests_pkcs11mtt_LDADD += src/libwolfpkcs11.la @@ -301,6 +306,7 @@ tests_logout_token_key_zero_test_LDADD += src/libwolfpkcs11.la tests_tpm_decode_bounds_test_LDADD += src/libwolfpkcs11.la tests_concurrent_destroy_object_test_LDADD += src/libwolfpkcs11.la tests_hbs_persistence_test_LDADD += src/libwolfpkcs11.la +tests_hkdf_persistence_test_LDADD += src/libwolfpkcs11.la else tests_object_id_uniqueness_test_LDADD += src/libwolfpkcs11.la tests_empty_pin_store_test_LDADD += src/libwolfpkcs11.la @@ -344,6 +350,7 @@ tests_logout_token_key_zero_test_LDADD += src/libwolfpkcs11.la tests_tpm_decode_bounds_test_LDADD += src/libwolfpkcs11.la tests_concurrent_destroy_object_test_LDADD += src/libwolfpkcs11.la tests_hbs_persistence_test_LDADD += src/libwolfpkcs11.la +tests_hkdf_persistence_test_LDADD += src/libwolfpkcs11.la endif EXTRA_DIST += tests/unit.h \ diff --git a/tests/pkcs11test.c b/tests/pkcs11test.c index 90732c22..abe51a06 100644 --- a/tests/pkcs11test.c +++ b/tests/pkcs11test.c @@ -544,6 +544,7 @@ static CK_RV test_slot(void* args) CK_ULONG count; CK_MECHANISM_TYPE* list = NULL; CK_MECHANISM_INFO info; + int ssl3MasterFound = 0; int i; (void)session; @@ -625,6 +626,19 @@ static CK_RV test_slot(void* args) ret = funcList->C_GetMechanismList(slot, list, &count); CHECK_CKR(ret, "Get Mechanism List count"); } + if (ret == CKR_OK) { + for (i = 0; i < (int)count; i++) { + if (list[i] == CKM_SSL3_MASTER_KEY_DERIVE) + ssl3MasterFound = 1; + } +#ifdef WOLFPKCS11_NSS + CHECK_COND(ssl3MasterFound, ret, + "NSS SSL3 master target mechanism advertised"); +#else + CHECK_COND(!ssl3MasterFound, ret, + "Unimplemented SSL3 master derive not advertised"); +#endif + } if (ret == CKR_OK) { ret = funcList->C_GetMechanismInfo(0, list[0], &info); @@ -640,6 +654,20 @@ static CK_RV test_slot(void* args) CHECK_CKR_FAIL(ret, CKR_MECHANISM_INVALID, "Get Mechanism Info bad mech"); } + if (ret == CKR_OK) { + ret = funcList->C_GetMechanismInfo(slot, CKM_SSL3_MASTER_KEY_DERIVE, + &info); +#ifdef WOLFPKCS11_NSS + CHECK_CKR(ret, "Get NSS SSL3 master target mechanism info"); + if (ret == CKR_OK) { + CHECK_COND(info.flags == 0, ret, + "NSS SSL3 master target has no derive capability"); + } +#else + CHECK_CKR_FAIL(ret, CKR_MECHANISM_INVALID, + "Get Mechanism Info unimplemented SSL3 derive"); +#endif + } if (ret == CKR_OK) { for (i = 0; ret == CKR_OK && i < (int)count; i++) { ret = funcList->C_GetMechanismInfo(slot, list[i], &info); @@ -4641,6 +4669,72 @@ static CK_RV test_find_objects(void* args) return ret; } +static CK_RV test_find_objects_many(void* args) +{ + CK_SESSION_HANDLE session = *(CK_SESSION_HANDLE*)args; + CK_RV ret = CKR_OK; + CK_OBJECT_HANDLE objects[WP11_FIND_MAX + 1]; + CK_OBJECT_HANDLE found[7]; + CK_ULONG foundCount; + CK_ULONG total = 0; + int created = 0; + int findActive = 0; + int i; + static byte keyData[] = { 0x5a }; + static byte id[] = { 0x46, 0x38, 0x36, 0x35, 0x37 }; + CK_ATTRIBUTE tmpl[] = { + { CKA_CLASS, &secretKeyClass, sizeof(secretKeyClass) }, + { CKA_KEY_TYPE, &genericKeyType, sizeof(genericKeyType) }, + { CKA_VALUE, keyData, sizeof(keyData) }, + { CKA_ID, id, sizeof(id) }, + { CKA_TOKEN, &ckTrue, sizeof(ckTrue) }, + { CKA_PRIVATE, &ckFalse, sizeof(ckFalse) }, + }; + CK_ATTRIBUTE findTmpl[] = { + { CKA_ID, id, sizeof(id) }, + }; + + for (i = 0; ret == CKR_OK && i < (int)(WP11_FIND_MAX + 1); i++) { + ret = funcList->C_CreateObject(session, tmpl, + sizeof(tmpl) / sizeof(*tmpl), &objects[i]); + CHECK_CKR(ret, "Create object beyond former find cache limit"); + if (ret == CKR_OK) + created++; + } + + if (ret == CKR_OK) { + ret = funcList->C_FindObjectsInit(session, findTmpl, + sizeof(findTmpl) / sizeof(*findTmpl)); + CHECK_CKR(ret, "Find many objects init"); + if (ret == CKR_OK) + findActive = 1; + } + while (ret == CKR_OK) { + ret = funcList->C_FindObjects(session, found, + sizeof(found) / sizeof(*found), + &foundCount); + CHECK_CKR(ret, "Find many objects batch"); + if (ret != CKR_OK || foundCount == 0) + break; + total += foundCount; + } + if (ret == CKR_OK) { + CHECK_COND(total == WP11_FIND_MAX + 1, ret, + "Find returns objects beyond former cache limit"); + } + if (findActive) { + CK_RV finalRet = funcList->C_FindObjectsFinal(session); + CHECK_CKR(finalRet, "Find many objects final"); + if (ret == CKR_OK) + ret = finalRet; + } + + for (i = 0; i < created; i++) + funcList->C_DestroyObject(session, objects[i]); + + return ret; +} + static CK_RV get_aes_128_key(CK_SESSION_HANDLE session, unsigned char* id, int idLen, CK_OBJECT_HANDLE* key) { @@ -16964,6 +17058,16 @@ static CK_RV test_private_object_access(void* args) }; CK_ULONG findTmplCnt = sizeof(findTmpl) / sizeof(*findTmpl); CK_OBJECT_HANDLE found; +#ifndef WOLFPKCS11_NSS + CK_OBJECT_HANDLE soObj = CK_INVALID_HANDLE; +#else + CK_OBJECT_HANDLE nssObj = CK_INVALID_HANDLE; + CK_BBOOL nssSession = CK_FALSE; +#endif + CK_ULONG valueLen = 0; + CK_ATTRIBUTE getTmpl = { + CKA_VALUE_LEN, &valueLen, sizeof(valueLen) + }; CK_ULONG count; /* Create a private object while logged in (test setup logs us in) */ @@ -16994,8 +17098,79 @@ static CK_RV test_private_object_access(void* args) } } +#ifndef WOLFPKCS11_NSS + if (ret == CKR_OK) { + ret = funcList->C_Login(session, CKU_SO, soPin, soPinLen); + CHECK_CKR(ret, "Login SO for private object test"); + } + + if (ret == CKR_OK) { + ret = funcList->C_FindObjectsInit(session, findTmpl, findTmplCnt); + CHECK_CKR(ret, "Find Objects Init - SO logged in"); + if (ret == CKR_OK) { + ret = funcList->C_FindObjects(session, &found, 1, &count); + CHECK_CKR(ret, "Find Objects - SO logged in"); + } + if (ret == CKR_OK && count != 0) { + ret = -1; + CHECK_CKR(ret, "SO must not discover private objects"); + } + if (ret == CKR_OK) { + ret = funcList->C_FindObjectsFinal(session); + CHECK_CKR(ret, "Find Objects Final - SO logged in"); + } + } + + if (ret == CKR_OK) { + ret = funcList->C_GetAttributeValue(session, obj, &getTmpl, 1); + CHECK_CKR_FAIL(ret, CKR_OBJECT_HANDLE_INVALID, + "SO must not resolve private object handles"); + } + + if (ret == CKR_OK) { + ret = funcList->C_CreateObject(session, tmpl, tmplCnt, &soObj); + CHECK_CKR_FAIL(ret, CKR_USER_NOT_LOGGED_IN, + "SO must not create private objects"); + } + + if (ret == CKR_OK) { + ret = funcList->C_Logout(session); + CHECK_CKR(ret, "Logout SO for private object test"); + } +#else + /* NSS is an internal crypto module with an established SO-session + * exception to the standard private-object rules. Keep that compatibility + * behavior while F-8650 tightens only the default PKCS#11 build. */ + if (ret == CKR_OK) { + ret = funcList->C_Login(session, CKU_SO, soPin, soPinLen); + CHECK_CKR(ret, "Login SO for NSS private object test"); + } + + if (ret == CKR_OK) { + tmpl[4].pValue = &nssSession; + ret = funcList->C_CreateObject(session, tmpl, tmplCnt, &nssObj); + CHECK_CKR(ret, "NSS SO creates private session object"); + } + + if (ret == CKR_OK) { + ret = funcList->C_GetAttributeValue(session, nssObj, &getTmpl, 1); + CHECK_CKR(ret, "NSS SO resolves private session object"); + } + + if (nssObj != CK_INVALID_HANDLE) { + funcList->C_DestroyObject(session, nssObj); + nssObj = CK_INVALID_HANDLE; + } + tmpl[4].pValue = &ckTrue; + + if (ret == CKR_OK) { + ret = funcList->C_Logout(session); + CHECK_CKR(ret, "Logout SO for NSS private object test"); + } +#endif + if (ret == CKR_OK) { - /* Login as user */ + /* Login as user. */ ret = funcList->C_Login(session, CKU_USER, userPin, userPinLen); CHECK_CKR(ret, "Login for private object test"); } @@ -18097,6 +18272,7 @@ static TEST_FUNC testFunc[] = { #endif PKCS11TEST_FUNC_SESS_DECL(test_get_attr_value_all_processed), PKCS11TEST_FUNC_SESS_DECL(test_find_objects), + PKCS11TEST_FUNC_SESS_DECL(test_find_objects_many), PKCS11TEST_FUNC_SESS_DECL(test_private_object_access), #ifndef WOLFPKCS11_NSS PKCS11TEST_FUNC_SESS_DECL(test_private_object_handle_access), diff --git a/tests/pkcs11v3test.c b/tests/pkcs11v3test.c index b88ff5fa..ae5236f3 100644 --- a/tests/pkcs11v3test.c +++ b/tests/pkcs11v3test.c @@ -2440,6 +2440,18 @@ static CK_RV test_get_interface(void* args) ret = ((CK_C_GetInterface)func)(interfaceName, NULL, &interface, 0); CHECK_CKR(ret, "Get Interface"); } + if (ret == CKR_OK) { + version.major = 2; + version.minor = 20; + ret = ((CK_C_GetInterface)func)(NULL, &version, &interface, 0); + CHECK_CKR_FAIL(ret, CKR_ARGUMENTS_BAD, + "Get default Interface with unsupported version"); + } + if (ret == CKR_OK) { + ret = ((CK_C_GetInterface)func)(NULL, NULL, &interface, 1); + CHECK_CKR_FAIL(ret, CKR_ARGUMENTS_BAD, + "Get default Interface with unsupported flags"); + } if (ret == CKR_OK) { interfaceName = (CK_UTF8CHAR_PTR)"FAIL"; ret = ((CK_C_GetInterface)func)(interfaceName, NULL, &interface, flags); @@ -2489,6 +2501,18 @@ static CK_RV test_get_interface(void* args) ret = C_GetInterface(interfaceName, NULL, &interface, 0); CHECK_CKR(ret, "Get Interface"); } + if (ret == CKR_OK) { + version.major = 2; + version.minor = 20; + ret = C_GetInterface(NULL, &version, &interface, 0); + CHECK_CKR_FAIL(ret, CKR_ARGUMENTS_BAD, + "Get default Interface with unsupported version"); + } + if (ret == CKR_OK) { + ret = C_GetInterface(NULL, NULL, &interface, 1); + CHECK_CKR_FAIL(ret, CKR_ARGUMENTS_BAD, + "Get default Interface with unsupported flags"); + } if (ret == CKR_OK) { interfaceName = (CK_UTF8CHAR_PTR)"FAIL"; ret = C_GetInterface(interfaceName, NULL, &interface, flags); diff --git a/wolfpkcs11/internal.h b/wolfpkcs11/internal.h index 1c1a73d4..0b67003d 100644 --- a/wolfpkcs11/internal.h +++ b/wolfpkcs11/internal.h @@ -201,7 +201,7 @@ C_EXTRA_FLAGS="-DWOLFSSL_PUBLIC_MP -DWC_RSA_DIRECT" #define WP11_FIND_STATE_NULL 0 #define WP11_FIND_STATE_INIT 1 #define WP11_FIND_STATE_FOUND 2 -/* Maximum number of matching objects to hold handles of. */ +/* Initial number of matching object handles to allocate. */ #ifndef WP11_FIND_MAX #ifdef WOLFPKCS11_NSS #define WP11_FIND_MAX 100 @@ -378,6 +378,7 @@ C_EXTRA_FLAGS="-DWOLFSSL_PUBLIC_MP -DWC_RSA_DIRECT" #define OBJ_TYPE_E -11 #define PARAM_E -12 #define LOGGED_IN_ANOTHER_E -13 +#define WP11_CTR_OVERFLOW_E -14 typedef struct WP11_Object WP11_Object; @@ -408,6 +409,7 @@ WP11_LOCAL int WP11_Slot_SOPin_IsSet(WP11_Slot* slot); WP11_LOCAL int WP11_Slot_SOLogin(WP11_Slot* slot, char* pin, int pinLen); WP11_LOCAL int WP11_Slot_UserLogin(WP11_Slot* slot, char* pin, int pinLen); WP11_LOCAL int WP11_Slot_IsLoggedIn(WP11_Slot* slot); +WP11_LOCAL int WP11_Slot_IsUserLoggedIn(WP11_Slot* slot); WP11_LOCAL void WP11_Slot_Logout(WP11_Slot* slot); #ifdef DEBUG_WOLFPKCS11 WP11_API int WP11_Slot_TokenKeyIsZero(CK_SLOT_ID slotId); @@ -488,8 +490,8 @@ WP11_LOCAL void WP11_Session_GetObject(WP11_Session* session, WP11_Object** obje WP11_LOCAL void WP11_Session_SetObject(WP11_Session* session, WP11_Object* object); WP11_LOCAL int WP11_Session_FindInit(WP11_Session* session); -WP11_LOCAL void WP11_Session_Find(WP11_Session* session, int onToken, - CK_ATTRIBUTE_PTR pTemplate, CK_ULONG ulCount); +WP11_LOCAL int WP11_Session_Find(WP11_Session* session, int onToken, + CK_ATTRIBUTE_PTR pTemplate, CK_ULONG ulCount); WP11_LOCAL int WP11_Session_FindGet(WP11_Session* session, CK_OBJECT_HANDLE* id); WP11_LOCAL void WP11_Session_FindFinal(WP11_Session* session); WP11_LOCAL int WP11_Session_IsFindActive(WP11_Session* session);