Skip to content

Commit 9185525

Browse files
authored
Merge pull request #207 from julek-wolfssl/wolfpkcs11-fenrir-security-fixes
PKCS11 access-control, session-state, and concurrency hardening
2 parents 0dd0779 + fab1cb1 commit 9185525

6 files changed

Lines changed: 217 additions & 23 deletions

File tree

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,14 @@ versions may need to update templates or error-handling:
144144
(`--enable-nss`) keep the empty-PIN probe accepted on uninitialized
145145
tokens because `PK11_InitPin` bootstraps an empty-password NSS
146146
database that way; non-empty PINs are still rejected.
147+
- On `--enable-nss` builds `WP11_MIN_PIN_LEN` defaults to `0`, permitting a
148+
zero-length user PIN. An empty user PIN intentionally disables PIN-based
149+
authentication: `C_GetTokenInfo` clears `CKF_LOGIN_REQUIRED` and all
150+
private/token objects are decoded and accessible at token load without
151+
`C_Login`. This is required for NSS tools (`certutil`, `PK11_InitPin`) that
152+
bootstrap empty-password databases. Integrators who require an enforced
153+
minimum can opt in at build time with `C_EXTRA_FLAGS="-DWP11_MIN_PIN_LEN=N"`
154+
(`N>0`); non-NSS builds already default to `4`.
147155

148156
#### Analog Devices, Inc. MAXQ10xx Secure Elements ([MAXQ1065](https://www.analog.com/en/products/maxq1065.html)/MAXQ1080)
149157

src/crypto.c

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1132,6 +1132,17 @@ static CK_RV SetAttributeValue(WP11_Session* session, WP11_Object* obj,
11321132
*(CK_BBOOL*)attr->pValue == CK_TRUE)
11331133
return CKR_ATTRIBUTE_READ_ONLY;
11341134
}
1135+
/* PKCS#11 v2.40 sec 4.5: only an SO session may set CKA_TRUSTED to
1136+
* CK_TRUE. A regular-user session must not forge trust and bypass the
1137+
* CKA_WRAP_WITH_TRUSTED export gate enforced by C_WrapKey. Not
1138+
* qualified with !newObject so it also stops C_CreateObject /
1139+
* C_GenerateKey from minting a trusted key. CheckAttributes above has
1140+
* already validated CKA_TRUSTED as a well-formed CK_BBOOL. */
1141+
if (attr->type == CKA_TRUSTED &&
1142+
*(CK_BBOOL*)attr->pValue == CK_TRUE &&
1143+
WP11_Session_GetState(session) != WP11_APP_STATE_RW_SO) {
1144+
return CKR_ATTRIBUTE_READ_ONLY;
1145+
}
11351146
/* These class/identity and generated-state attributes are read-only
11361147
* once the object exists; reject a change. Setting the current value
11371148
* is a no-op. */
@@ -1720,6 +1731,18 @@ CK_RV C_CopyObject(CK_SESSION_HANDLE hSession, CK_OBJECT_HANDLE hObject,
17201731
return rv;
17211732
}
17221733

1734+
/* Creating a private object requires an authenticated user session. The
1735+
* copy template can set CKA_PRIVATE=CK_TRUE, so gate it like
1736+
* C_CreateObject. The copy's default CKA_PRIVATE is inherited from the
1737+
* source object, whose own login requirement was already enforced by
1738+
* WP11_Object_Find above, so only an explicit template override is checked
1739+
* here (WP11_NO_IMPLICIT_CLASS = template inspection only). */
1740+
rv = CheckPrivateLogin(session, pTemplate, ulCount, WP11_NO_IMPLICIT_CLASS);
1741+
if (rv != CKR_OK) {
1742+
WOLFPKCS11_LEAVE("C_CopyObject", rv);
1743+
return rv;
1744+
}
1745+
17231746
keyType = WP11_Object_GetType(obj);
17241747

17251748
/* The copy inherits the source's CKA_TOKEN (PKCS#11 v2.40 4.6.2) unless

src/internal.c

Lines changed: 126 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -647,6 +647,20 @@ static wolfSSL_Mutex libraryInitLock
647647
#define WP11_HAVE_LIBRARY_INIT_LOCK
648648
#endif
649649

650+
#if !defined(SINGLE_THREADED) && defined(WOLFSSL_MUTEX_INITIALIZER) && \
651+
defined(WOLFSSL_MUTEX_INITIALIZER_CLAUSE) && \
652+
!defined(WOLFPKCS11_TPM_STORE) && defined(WOLFPKCS11_NSS)
653+
/* Permanently-live leaf mutex serializing the module-global storeDir, which
654+
* is set at C_Initialize (before globalLock exists), read in
655+
* wolfPKCS11_Store_Name, and freed in WP11_Library_Final after globalLock is
656+
* released. Without it the free races the set/read (Fenrir F-5868, F-5150).
657+
* Static init mirrors libraryInitLock. It is always acquired as a leaf (no
658+
* other lock is taken while it is held), so it cannot invert any ordering. */
659+
static wolfSSL_Mutex storeDirLock
660+
WOLFSSL_MUTEX_INITIALIZER_CLAUSE(storeDirLock);
661+
#define WP11_HAVE_STORE_DIR_LOCK
662+
#endif
663+
650664

651665
#ifndef SINGLE_THREADED
652666
/**
@@ -1155,17 +1169,30 @@ static char* storeDir = NULL;
11551169

11561170
int WP11_SetStoreDir(const char *dir, size_t dirSz)
11571171
{
1172+
int ret = 0;
1173+
#ifdef WP11_HAVE_STORE_DIR_LOCK
1174+
/* Serialize against the free in WP11_Library_Final and any concurrent set
1175+
* so the XFREE/XMALLOC/XMEMCPY sequence is not raced (F-5868). */
1176+
if (wc_LockMutex(&storeDirLock) != 0)
1177+
return BAD_MUTEX_E;
1178+
#endif
11581179
if (storeDir != NULL)
11591180
XFREE(storeDir, NULL, DYNAMIC_TYPE_TMP_BUFFER);
11601181
storeDir = NULL;
11611182
if (dir != NULL) {
11621183
storeDir = (char*) XMALLOC(dirSz + 1, NULL, DYNAMIC_TYPE_TMP_BUFFER);
1163-
if (storeDir == NULL)
1164-
return MEMORY_E;
1165-
XMEMCPY(storeDir, dir, dirSz);
1166-
storeDir[dirSz] = '\0'; /* Ensure null termination */
1184+
if (storeDir == NULL) {
1185+
ret = MEMORY_E;
1186+
}
1187+
else {
1188+
XMEMCPY(storeDir, dir, dirSz);
1189+
storeDir[dirSz] = '\0'; /* Ensure null termination */
1190+
}
11671191
}
1168-
return 0;
1192+
#ifdef WP11_HAVE_STORE_DIR_LOCK
1193+
wc_UnLockMutex(&storeDirLock);
1194+
#endif
1195+
return ret;
11691196
}
11701197
#endif
11711198

@@ -1358,6 +1385,9 @@ static int wolfPKCS11_Store_Name(int type, CK_ULONG id1, CK_ULONG id2, char* nam
13581385
*/
13591386
enum { WP11_STORE_SUFFIX_RESERVE = 48 };
13601387
char homePath[256];
1388+
#ifdef WP11_HAVE_STORE_DIR_LOCK
1389+
char storeDirCopy[WP11_STORE_MAX_PATH];
1390+
#endif
13611391

13621392
/* Path order:
13631393
* 1. Environment variable WOLFPKCS11_TOKEN_PATH
@@ -1371,9 +1401,28 @@ static int wolfPKCS11_Store_Name(int type, CK_ULONG id1, CK_ULONG id2, char* nam
13711401
#endif
13721402

13731403
#ifdef WOLFPKCS11_NSS
1374-
if (str == NULL)
1404+
if (str == NULL) {
1405+
#ifdef WP11_HAVE_STORE_DIR_LOCK
1406+
/* Copy storeDir into a local under storeDirLock so a concurrent
1407+
* WP11_Library_Final free cannot leave str dangling while we format
1408+
* the path below (F-5150). The lock is released before use. */
1409+
if (wc_LockMutex(&storeDirLock) != 0)
1410+
return -1;
1411+
if (storeDir != NULL) {
1412+
size_t sdLen = XSTRLEN(storeDir);
1413+
if (sdLen >= sizeof(storeDirCopy)) {
1414+
wc_UnLockMutex(&storeDirLock);
1415+
return -1;
1416+
}
1417+
XMEMCPY(storeDirCopy, storeDir, sdLen + 1);
1418+
str = storeDirCopy;
1419+
}
1420+
wc_UnLockMutex(&storeDirLock);
1421+
#else
13751422
str = storeDir;
13761423
#endif
1424+
}
1425+
#endif
13771426

13781427
if (str == NULL) {
13791428
const char* homeDir = NULL;
@@ -3352,8 +3401,11 @@ static int wp11_Object_Load_Data(WP11_Object* object, int tokenId, int objId)
33523401
#ifdef WOLFSSL_MAXQ10XX_CRYPTO
33533402
#ifdef MAXQ10XX_PRODUCTION_KEY
33543403
#include "maxq10xx_key.h"
3355-
#else
3356-
/* TEST KEY. This must be changed for production environments!! */
3404+
#elif defined(WOLFPKCS11_MAXQ10XX_TEST_KEY)
3405+
/* INSECURE TEST KEY. The private scalar (last 32 bytes) is public in the
3406+
* source, so anyone can forge a valid MXQ_ImportRootCert provisioning
3407+
* signature for an arbitrary root certificate. For development against the
3408+
* MAXQ10xx evaluation kit only - never ship this. */
33573409
static mxq_u1 KeyPairImport[] = {
33583410
0xd0,0x97,0x31,0xc7,0x63,0xc0,0x9e,0xe3,0x9a,0xb4,0xd0,0xce,0xa7,0x89,0xab,
33593411
0x52,0xc8,0x80,0x3a,0x91,0x77,0x29,0xc3,0xa0,0x79,0x2e,0xe6,0x61,0x8b,0x2d,
@@ -3363,6 +3415,8 @@ static mxq_u1 KeyPairImport[] = {
33633415
0x72,0x5e,0x88,0xaf,0xc2,0xee,0x8b,0x6f,0xe5,0x36,0xe3,0x60,0x7c,0xf8,0x2c,
33643416
0xea,0x3a,0x4f,0xe3,0x6d,0x73
33653417
};
3418+
#else
3419+
#error "MAXQ10xx root-cert provisioning key is undefined. Define MAXQ10XX_PRODUCTION_KEY (with a real maxq10xx_key.h) for production, or WOLFPKCS11_MAXQ10XX_TEST_KEY to explicitly opt into the built-in INSECURE test key for evaluation-kit development."
33663420
#endif /* MAXQ10XX_PRODUCTION_KEY */
33673421

33683422
static int crypto_sha256(const byte *buf, word32 len, byte *hash,
@@ -7722,8 +7776,22 @@ void WP11_Library_Final(void)
77227776
(void)ret; /* store failure cannot be returned, so log and ignore */
77237777
}
77247778
#if !defined (WOLFPKCS11_CUSTOM_STORE) && defined(WOLFPKCS11_NSS)
7725-
XFREE(storeDir, NULL, DYNAMIC_TYPE_TMP_BUFFER);
7726-
storeDir = NULL;
7779+
/* Serialize the free against a concurrent WP11_SetStoreDir /
7780+
* wolfPKCS11_Store_Name (F-5868, F-5150). Nested inside libraryInitLock
7781+
* (held here); storeDirLock is a leaf so the fixed order
7782+
* libraryInitLock -> storeDirLock cannot deadlock. */
7783+
{
7784+
#ifdef WP11_HAVE_STORE_DIR_LOCK
7785+
int storeDirLocked = (wc_LockMutex(&storeDirLock) == 0);
7786+
#endif
7787+
XFREE(storeDir, NULL, DYNAMIC_TYPE_TMP_BUFFER);
7788+
storeDir = NULL;
7789+
#ifdef WP11_HAVE_STORE_DIR_LOCK
7790+
/* Only unlock if the lock was actually acquired. */
7791+
if (storeDirLocked)
7792+
wc_UnLockMutex(&storeDirLock);
7793+
#endif
7794+
}
77277795
#endif
77287796
#endif
77297797
/* Cleanup the slots. */
@@ -7988,6 +8056,12 @@ void WP11_Slot_CloseSessions(WP11_Slot* slot)
79888056
for (curr = slot->session; curr != NULL; curr = curr->next)
79898057
wp11_Session_Final(curr);
79908058
WP11_Lock_UnlockRW(&slot->lock);
8059+
8060+
/* PKCS#11: closing an application's last session with a token logs the
8061+
* application out. Mirror the single-session close path and reset the
8062+
* token login state (outside the slot lock, as WP11_Slot_Logout takes it).
8063+
*/
8064+
WP11_Slot_Logout(slot);
79918065
}
79928066

79938067
/**
@@ -8056,6 +8130,21 @@ static int HashPIN(char* pin, int pinLen, byte* seed, int seedLen, byte* hash,
80568130
WP11_HASH_PIN_COST, WP11_HASH_PIN_BLOCKSIZE,
80578131
WP11_HASH_PIN_PARALLEL, hashLen);
80588132
#elif !defined(NO_SHA256)
8133+
/* Fallback: unsalted single-pass SHA-256 of the PIN. This provides no
8134+
* salt (the per-token seed is discarded) and no key stretching, so an
8135+
* attacker with the token-store file can brute-force a weak PIN offline
8136+
* and recover the token storage key. Configure WOLFPKCS11_PBKDF2 or
8137+
* HAVE_SCRYPT for a salted, stretched KDF. The selection is compile-time
8138+
* and otherwise silent, so warn integrators unless they opt out (F-6232).
8139+
* Note: changing this derivation would invalidate existing token stores,
8140+
* so hardening it in place is left as a deliberate maintainer decision. */
8141+
#ifndef WOLFPKCS11_ALLOW_WEAK_PIN_KDF
8142+
#if defined(_MSC_VER)
8143+
#pragma message("wolfPKCS11: no PBKDF2/scrypt - PIN hashing and token key derivation use unsalted SHA-256; define WOLFPKCS11_PBKDF2 or HAVE_SCRYPT for a strong KDF, or WOLFPKCS11_ALLOW_WEAK_PIN_KDF to silence")
8144+
#elif defined(__GNUC__) || defined(__clang__)
8145+
#warning "wolfPKCS11: no PBKDF2/scrypt - PIN hashing and token key derivation use unsalted SHA-256; define WOLFPKCS11_PBKDF2 or HAVE_SCRYPT for a strong KDF, or WOLFPKCS11_ALLOW_WEAK_PIN_KDF to silence"
8146+
#endif
8147+
#endif
80598148
/* fallback to simple SHA2-256 hash of pin */
80608149
(void)seed;
80618150
(void)seedLen;
@@ -9461,6 +9550,13 @@ int WP11_Session_SetCbcParams(WP11_Session* session, unsigned char* iv,
94619550
WP11_CbcParams* cbc = &session->params.cbc;
94629551
WP11_Data* key;
94639552

9553+
/* The session params union is shared by every mechanism and is only zeroed
9554+
* at allocation, so a prior operation can leave stale multi-part streaming
9555+
* state here. Reset it before use (as the other Set*Params routines do) so
9556+
* a fresh CBC operation cannot inherit a bogus partial-block count. */
9557+
cbc->partialSz = 0;
9558+
XMEMSET(cbc->partial, 0, sizeof(cbc->partial));
9559+
94649560
/* AES object on session. */
94659561
ret = wc_AesInit(&cbc->aes, NULL, object->devId);
94669562
#ifdef WOLFSSL_STM32U5_DHUK
@@ -15453,6 +15549,13 @@ int WP11_AesCbc_EncryptUpdate(unsigned char* plain, word32 plainSz,
1545315549
int sz = 0;
1545415550
int outSz = 0;
1545515551

15552+
/* Serialize the read-modify-write of cbc->partial/partialSz. Without this
15553+
* two threads sharing one session handle can both read partialSz, both
15554+
* copy into cbc->partial and both add, driving partialSz past
15555+
* AES_BLOCK_SIZE so the next call computes a negative sz and overflows the
15556+
* 16-byte partial buffer (F-5764). No caller holds slot->lock here. */
15557+
WP11_Lock_LockRW(&session->slot->lock);
15558+
1545615559
if (cbc->partialSz > 0) {
1545715560
sz = AES_BLOCK_SIZE - cbc->partialSz;
1545815561
if (sz > (int)plainSz)
@@ -15486,6 +15589,7 @@ int WP11_AesCbc_EncryptUpdate(unsigned char* plain, word32 plainSz,
1548615589
if (ret == 0)
1548715590
*encSz = outSz;
1548815591

15592+
WP11_Lock_UnlockRW(&session->slot->lock);
1548915593
return ret;
1549015594
}
1549115595

@@ -15560,6 +15664,10 @@ int WP11_AesCbc_DecryptUpdate(unsigned char* enc, word32 encSz,
1556015664
int sz = 0;
1556115665
int outSz = 0;
1556215666

15667+
/* Serialize the partial-block read-modify-write against a concurrent
15668+
* update on the same session (F-5764); see WP11_AesCbc_EncryptUpdate. */
15669+
WP11_Lock_LockRW(&session->slot->lock);
15670+
1556315671
if (cbc->partialSz > 0) {
1556415672
sz = AES_BLOCK_SIZE - cbc->partialSz;
1556515673
if (sz > (int)encSz)
@@ -15592,6 +15700,7 @@ int WP11_AesCbc_DecryptUpdate(unsigned char* enc, word32 encSz,
1559215700
if (ret == 0)
1559315701
*decSz = outSz;
1559415702

15703+
WP11_Lock_UnlockRW(&session->slot->lock);
1559515704
return ret;
1559615705
}
1559715706

@@ -15764,6 +15873,10 @@ int WP11_AesCbcPad_DecryptUpdate(unsigned char* enc, word32 encSz,
1576415873
int sz = 0;
1576515874
int outSz = 0;
1576615875

15876+
/* Serialize the partial-block read-modify-write against a concurrent
15877+
* update on the same session (F-5764); see WP11_AesCbc_EncryptUpdate. */
15878+
WP11_Lock_LockRW(&session->slot->lock);
15879+
1576715880
if (cbc->partialSz > 0) {
1576815881
sz = AES_BLOCK_SIZE - cbc->partialSz;
1576915882
if (sz > (int)encSz)
@@ -15777,6 +15890,7 @@ int WP11_AesCbcPad_DecryptUpdate(unsigned char* enc, word32 encSz,
1577715890
* far and leave the operation active (CKR_BUFFER_TOO_SMALL). */
1577815891
if ((word32)(outSz + AES_BLOCK_SIZE) > bufSz) {
1577915892
*decSz = (word32)outSz + AES_BLOCK_SIZE;
15893+
WP11_Lock_UnlockRW(&session->slot->lock);
1578015894
return BUFFER_E;
1578115895
}
1578215896
ret = wc_AesCbcDecrypt(&cbc->aes, dec, cbc->partial,
@@ -15792,6 +15906,7 @@ int WP11_AesCbcPad_DecryptUpdate(unsigned char* enc, word32 encSz,
1579215906
sz -= AES_BLOCK_SIZE;
1579315907
if ((word32)(outSz + sz) > bufSz) {
1579415908
*decSz = (word32)(outSz + sz);
15909+
WP11_Lock_UnlockRW(&session->slot->lock);
1579515910
return BUFFER_E;
1579615911
}
1579715912
ret = wc_AesCbcDecrypt(&cbc->aes, dec, enc, sz);
@@ -15806,6 +15921,7 @@ int WP11_AesCbcPad_DecryptUpdate(unsigned char* enc, word32 encSz,
1580615921
if (ret == 0)
1580715922
*decSz = outSz;
1580815923

15924+
WP11_Lock_UnlockRW(&session->slot->lock);
1580915925
return ret;
1581015926
}
1581115927

src/slot.c

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -391,7 +391,7 @@ static CK_MECHANISM_TYPE mechanismList[] = {
391391
#endif
392392
#ifndef NO_AES
393393
CKM_AES_KEY_GEN,
394-
#ifdef HAVE_AES_KEY_WRAP
394+
#ifdef HAVE_AES_KEYWRAP
395395
CKM_AES_KEY_WRAP,
396396
CKM_AES_KEY_WRAP_PAD,
397397
#endif
@@ -546,6 +546,9 @@ CK_RV C_GetMechanismList(CK_SLOT_ID slotID,
546546
return rv;
547547
}
548548
else if (*pulCount < (CK_ULONG)mechanismCnt) {
549+
/* PKCS#11: on CKR_BUFFER_TOO_SMALL *pulCount must be set to the
550+
* required count so the two-call (size-query then fetch) idiom works. */
551+
*pulCount = mechanismCnt;
549552
rv = CKR_BUFFER_TOO_SMALL;
550553
WOLFPKCS11_LEAVE("C_GetMechanismList", rv);
551554
return rv;
@@ -737,7 +740,7 @@ static CK_MECHANISM_INFO tlsMacMechInfo = {
737740
static CK_MECHANISM_INFO aesKeyGenMechInfo = {
738741
16, 32, CKF_GENERATE
739742
};
740-
#ifdef HAVE_AES_KEY_WRAP
743+
#ifdef HAVE_AES_KEYWRAP
741744
static CK_MECHANISM_INFO aesKeyWrapMechInfo = {
742745
16, 32, CKF_ENCRYPT | CKF_DECRYPT | CKF_WRAP | CKF_UNWRAP
743746
};
@@ -1061,7 +1064,7 @@ CK_RV C_GetMechanismInfo(CK_SLOT_ID slotID, CK_MECHANISM_TYPE type,
10611064
case CKM_AES_KEY_GEN:
10621065
XMEMCPY(pInfo, &aesKeyGenMechInfo, sizeof(CK_MECHANISM_INFO));
10631066
break;
1064-
#ifdef HAVE_AES_KEY_WRAP
1067+
#ifdef HAVE_AES_KEYWRAP
10651068
case CKM_AES_KEY_WRAP:
10661069
case CKM_AES_KEY_WRAP_PAD:
10671070
XMEMCPY(pInfo, &aesKeyWrapMechInfo, sizeof(CK_MECHANISM_INFO));
@@ -1315,12 +1318,16 @@ CK_RV C_InitToken(CK_SLOT_ID slotID, CK_UTF8CHAR_PTR pPin,
13151318
return rv;
13161319
}
13171320

1321+
/* PKCS#11: an open session on the token must fail C_InitToken with
1322+
* CKR_SESSION_EXISTS regardless of whether the token is already
1323+
* initialized. Check this unconditionally before any token-reset logic. */
1324+
if (WP11_Slot_HasSession(slot)) {
1325+
rv = CKR_SESSION_EXISTS;
1326+
WOLFPKCS11_LEAVE("C_InitToken", rv);
1327+
return rv;
1328+
}
1329+
13181330
if (WP11_Slot_IsTokenInitialized(slot)) {
1319-
if (WP11_Slot_HasSession(slot)) {
1320-
rv = CKR_SESSION_EXISTS;
1321-
WOLFPKCS11_LEAVE("C_InitToken", rv);
1322-
return rv;
1323-
}
13241331
if (WP11_Slot_SOPin_IsSet(slot)) {
13251332
/* Verify the SO PIN with the failed-login lockout applied, so this
13261333
* path cannot be used to brute-force the SO PIN (Fenrir F-4632).

0 commit comments

Comments
 (0)