From 5891f806801b4313010dc23e2663e1f41f8925db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Thu, 22 Oct 2026 13:00:38 +0200 Subject: [PATCH 1/3] Share the PKCS#11 login between tokens of one application PKCS#11 keeps the login state per application, not per session. When a second Pkcs11Token for the same token logs in, C_Login returns CKR_USER_ALREADY_LOGGED_IN, which wolfSSL turned into WC_HW_E. NXP's SMW module does this for every token after the first. Treat CKR_USER_ALREADY_LOGGED_IN as logged in. A session that had logged in also called C_Logout before C_CloseSession. C_Logout ends the login of every session of the application, so closing a per-operation session of one token logged out the open session of any other token, and its private key operations then failed. PKCS#11 returns the application to public sessions when its last session on the token closes, and wc_Pkcs11Token_Final closes all of them, so wolfSSL no longer calls C_Logout. --- doc/dox_comments/header_files/pkcs11.h | 8 ++ tests/api/test_pkcs11.c | 110 ++++++++++++++++++++++++- tests/api/test_pkcs11.h | 2 + wolfcrypt/src/wc_pkcs11.c | 5 +- wolfssl/wolfcrypt/pkcs11.h | 1 + 5 files changed, 121 insertions(+), 5 deletions(-) diff --git a/doc/dox_comments/header_files/pkcs11.h b/doc/dox_comments/header_files/pkcs11.h index 7871ab43317..682342083f9 100644 --- a/doc/dox_comments/header_files/pkcs11.h +++ b/doc/dox_comments/header_files/pkcs11.h @@ -42,6 +42,14 @@ int wc_Pkcs11Token_Open(Pkcs11Token* token, int readWrite); /*! \ingroup PKCS11 + + \brief Closes the session opened with wc_Pkcs11Token_Open. It does not log + out, so the application stays logged in while another of its sessions on + the token is open. + + \param token Token whose session to close. + + \sa wc_Pkcs11Token_Open */ void wc_Pkcs11Token_Close(Pkcs11Token* token); diff --git a/tests/api/test_pkcs11.c b/tests/api/test_pkcs11.c index 09e54e79385..7cdb3c747b9 100644 --- a/tests/api/test_pkcs11.c +++ b/tests/api/test_pkcs11.c @@ -733,7 +733,6 @@ int test_wc_Pkcs11_FreeKeepsTokenKey(void) return EXPECT_RESULT(); } -#ifdef TEST_PKCS11_EC_KEYGEN static int test_pkcs11_make_ecc(ecc_key* key, WC_RNG* rng, const byte* id, int idLen) { @@ -747,7 +746,6 @@ static int test_pkcs11_make_ecc(ecc_key* key, WC_RNG* rng, const byte* id, #endif return ret; } -#endif #if defined(TEST_PKCS11_EC_KEYGEN) && defined(HAVE_ECC_KEY_IMPORT) static CK_OBJECT_HANDLE test_pkcs11_find_obj(CK_OBJECT_CLASS cls, @@ -1151,6 +1149,10 @@ int test_wc_Pkcs11_EcKeyUsage(void) return EXPECT_RESULT(); } +static CK_RV test_pkcs11_login_rv = CKR_OK; +static int test_pkcs11_logins = 0; +static int test_pkcs11_logouts = 0; +static int test_pkcs11_closes = 0; static int test_pkcs11_opens = 0; static CK_RV test_pkcs11_open_session(CK_SLOT_ID slotId, CK_FLAGS flags, @@ -1166,13 +1168,117 @@ static CK_RV test_pkcs11_open_session(CK_SLOT_ID slotId, CK_FLAGS flags, return CKR_OK; } +static CK_RV test_pkcs11_login(CK_SESSION_HANDLE session, CK_USER_TYPE type, + CK_UTF8CHAR_PTR pin, CK_ULONG pinLen) +{ + (void)session; + (void)type; + (void)pin; + (void)pinLen; + + test_pkcs11_logins++; + return test_pkcs11_login_rv; +} + +static CK_RV test_pkcs11_logout(CK_SESSION_HANDLE session) +{ + (void)session; + + test_pkcs11_logouts++; + return CKR_OK; +} + static CK_RV test_pkcs11_close_session(CK_SESSION_HANDLE session) { (void)session; + test_pkcs11_closes++; return CKR_OK; } +#ifdef HAVE_ECC_SIGN +static int test_pkcs11_sign_login(Pkcs11Token* token, ecc_key* key, + WC_RNG* rng, CK_RV loginRv) +{ + wc_CryptoInfo info; + byte hash[32]; + byte sig[ECC_MAX_SIG_SIZE]; + word32 sigLen = sizeof(sig); + + test_pkcs11_reset(); + test_pkcs11_login_rv = loginRv; + test_pkcs11_logouts = 0; + test_pkcs11_closes = 0; + XMEMSET(hash, 0x11, sizeof(hash)); + XMEMSET(&info, 0, sizeof(info)); + info.algo_type = WC_ALGO_TYPE_PK; + info.pk.type = WC_PK_TYPE_ECDSA_SIGN; + info.pk.eccsign.in = hash; + info.pk.eccsign.inlen = sizeof(hash); + info.pk.eccsign.out = sig; + info.pk.eccsign.outlen = &sigLen; + info.pk.eccsign.rng = rng; + info.pk.eccsign.key = key; + return wc_Pkcs11_CryptoDevCb(1, &info, token); +} +#endif + +int test_wc_Pkcs11_LoginShared(void) +{ + static const byte id[] = { 0x12 }; + Pkcs11Token token; + CK_FUNCTION_LIST func; + ecc_key key; + WC_RNG rng; + EXPECT_DECLS; + + test_pkcs11_store_setup(&token, &func); + func.C_OpenSession = test_pkcs11_open_session; + func.C_Login = test_pkcs11_login; + func.C_Logout = test_pkcs11_logout; + func.C_CloseSession = test_pkcs11_close_session; + token.handle = 0; + ExpectNotNull(token.userPin = (CK_UTF8CHAR_PTR)XMALLOC(4, NULL, + DYNAMIC_TYPE_TMP_BUFFER)); + if (token.userPin != NULL) + XMEMCPY(token.userPin, "1234", 4); + token.userPinSz = 4; + token.userPinLogin = 1; + XMEMSET(&key, 0, sizeof(key)); + ExpectIntEQ(wc_InitRng(&rng), 0); + ExpectIntEQ(test_pkcs11_make_ecc(&key, &rng, id, (int)sizeof(id)), 0); + +#ifdef HAVE_ECC_SIGN + ExpectIntEQ(test_pkcs11_sign_login(&token, &key, &rng, + CKR_USER_ALREADY_LOGGED_IN), 0); + ExpectIntEQ(test_pkcs11_logouts, 0); + ExpectIntEQ(test_pkcs11_closes, 1); + + ExpectIntEQ(test_pkcs11_sign_login(&token, &key, &rng, CKR_OK), 0); + ExpectIntEQ(test_pkcs11_logouts, 0); + ExpectIntEQ(test_pkcs11_closes, 1); + + ExpectIntEQ(test_pkcs11_sign_login(&token, &key, &rng, + TEST_CKR_GENERAL_ERROR), WC_NO_ERR_TRACE(WC_HW_E)); +#endif + + test_pkcs11_login_rv = CKR_USER_ALREADY_LOGGED_IN; + test_pkcs11_logouts = 0; + ExpectIntEQ(wc_Pkcs11Token_Open(&token, 1), 0); + wc_Pkcs11Token_Close(&token); + ExpectIntEQ(test_pkcs11_logouts, 0); + + test_pkcs11_login_rv = CKR_OK; + ExpectIntEQ(wc_Pkcs11Token_Open(&token, 1), 0); + wc_Pkcs11Token_Close(&token); + ExpectIntEQ(test_pkcs11_logouts, 0); + + wc_ecc_free(&key); + DoExpectIntEQ(wc_FreeRng(&rng), 0); + + return EXPECT_RESULT(); +} + int test_wc_Pkcs11_FreeNoObject(void) { Pkcs11Token token; diff --git a/tests/api/test_pkcs11.h b/tests/api/test_pkcs11.h index ff7089a6729..421ebf5ceb6 100644 --- a/tests/api/test_pkcs11.h +++ b/tests/api/test_pkcs11.h @@ -32,6 +32,7 @@ int test_wc_Pkcs11_EcdsaSigDecode(void); int test_wc_Pkcs11_FreeKeepsTokenKey(void); int test_wc_Pkcs11_RsaIdNoLeak(void); int test_wc_Pkcs11_EcKeyUsage(void); +int test_wc_Pkcs11_LoginShared(void); int test_wc_Pkcs11_RsaKeyGenError(void); int test_wc_Pkcs11_RsaKeyGenExponent(void); int test_wc_Pkcs11_FreeNoObject(void); @@ -42,6 +43,7 @@ int test_wc_Pkcs11_KeyGenSessionClosed(void); TEST_DECL_GROUP("pkcs11", test_wc_Pkcs11_FreeKeepsTokenKey), \ TEST_DECL_GROUP("pkcs11", test_wc_Pkcs11_RsaIdNoLeak), \ TEST_DECL_GROUP("pkcs11", test_wc_Pkcs11_EcKeyUsage), \ + TEST_DECL_GROUP("pkcs11", test_wc_Pkcs11_LoginShared), \ TEST_DECL_GROUP("pkcs11", test_wc_Pkcs11_RsaKeyGenError), \ TEST_DECL_GROUP("pkcs11", test_wc_Pkcs11_RsaKeyGenExponent), \ TEST_DECL_GROUP("pkcs11", test_wc_Pkcs11_FreeNoObject), \ diff --git a/wolfcrypt/src/wc_pkcs11.c b/wolfcrypt/src/wc_pkcs11.c index 09bdb96613d..a1668a994b9 100644 --- a/wolfcrypt/src/wc_pkcs11.c +++ b/wolfcrypt/src/wc_pkcs11.c @@ -1125,7 +1125,8 @@ static int Pkcs11OpenSession(Pkcs11Token* token, Pkcs11Session* session, rv = token->func->C_Login(session->handle, CKU_USER, token->userPin, token->userPinSz); PKCS11_RV("C_Login", rv); - if (rv != CKR_OK) { + /* Another token in this process may have logged in already. */ + if (rv != CKR_OK && rv != CKR_USER_ALREADY_LOGGED_IN) { ret = WC_HW_E; } } @@ -1151,8 +1152,6 @@ static int Pkcs11OpenSession(Pkcs11Token* token, Pkcs11Session* session, static void Pkcs11CloseSession(Pkcs11Token* token, Pkcs11Session* session) { if (token != NULL && session != NULL && token->handle != session->handle) { - if (token->userPin != NULL) - session->func->C_Logout(session->handle); session->func->C_CloseSession(session->handle); } } diff --git a/wolfssl/wolfcrypt/pkcs11.h b/wolfssl/wolfcrypt/pkcs11.h index d77a5ccfc76..910b9baf63c 100644 --- a/wolfssl/wolfcrypt/pkcs11.h +++ b/wolfssl/wolfcrypt/pkcs11.h @@ -202,6 +202,7 @@ extern "C" { #define CKR_OK 0x00000000UL #define CKR_MECHANISM_INVALID 0x00000070UL #define CKR_SIGNATURE_INVALID 0x000000C0UL +#define CKR_USER_ALREADY_LOGGED_IN 0x00000100UL #define CKR_CRYPTOKI_ALREADY_INITIALIZED 0x00000191UL #define CKD_NULL 0x00000001UL From c93b2853bfb4007bf4349d10a19772dbcd5cb33b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Thu, 22 Oct 2026 13:01:00 +0200 Subject: [PATCH 2/3] Keep a copy of the PKCS#11 user PIN in the token wc_Pkcs11Token_Init and wc_Pkcs11Token_InitName take the PIN as a const buffer but stored the caller's pointer, and wc_Pkcs11Token_Final then zeroed that buffer. A PIN in read-only memory, such as a string literal, crashed on finalize, and a caller's buffer had to stay valid for the token's lifetime. The token now allocates a copy of the PIN from the device heap, kept in a heap field appended to Pkcs11Token, and wc_Pkcs11Token_Final zeroes and frees that copy. An empty PIN gets a one-byte buffer, so a token initialized with a PIN always holds a non-NULL copy. A negative PIN length is rejected with BAD_FUNC_ARG before the token is set up. A pinOwned bit marks the token's own copy, so a PIN that the application assigned to the token itself is only forgotten, not freed. wc_Pkcs11Token_Final frees it only for a token that was set up, so finalizing a token whose initialization failed leaves its fields alone. --- doc/dox_comments/header_files/pkcs11.h | 14 ++++ tests/api/test_pkcs11.c | 109 ++++++++++++++++++++++++- tests/api/test_pkcs11.h | 2 + wolfcrypt/src/wc_pkcs11.c | 80 +++++++++++++++--- wolfssl/wolfcrypt/wc_pkcs11.h | 4 +- 5 files changed, 193 insertions(+), 16 deletions(-) diff --git a/doc/dox_comments/header_files/pkcs11.h b/doc/dox_comments/header_files/pkcs11.h index 682342083f9..96f20f9f24d 100644 --- a/doc/dox_comments/header_files/pkcs11.h +++ b/doc/dox_comments/header_files/pkcs11.h @@ -11,11 +11,25 @@ void wc_Pkcs11_Finalize(Pkcs11Dev* dev); /*! \ingroup PKCS11 + + \brief Sets up a token by slot id or name. A user PIN is copied into + memory the token owns. Call wc_Pkcs11Token_Final before initializing the + same token again, so that the copy is zeroized and freed. */ int wc_Pkcs11Token_Init(Pkcs11Token* token, Pkcs11Dev* dev, int slotId, const char* tokenName, const unsigned char *userPin, int userPinSz); +/*! + \ingroup PKCS11 + + \brief Sets up a token by name. A user PIN is copied as by + wc_Pkcs11Token_Init. +*/ +int wc_Pkcs11Token_InitName(Pkcs11Token* token, Pkcs11Dev* dev, + const char* tokenName, int tokenNameSz, + const unsigned char* userPin, int userPinSz); + /*! \ingroup PKCS11 */ diff --git a/tests/api/test_pkcs11.c b/tests/api/test_pkcs11.c index 7cdb3c747b9..3f936e6e9f5 100644 --- a/tests/api/test_pkcs11.c +++ b/tests/api/test_pkcs11.c @@ -1151,6 +1151,8 @@ int test_wc_Pkcs11_EcKeyUsage(void) static CK_RV test_pkcs11_login_rv = CKR_OK; static int test_pkcs11_logins = 0; +static byte test_pkcs11_login_pin[8]; +static CK_ULONG test_pkcs11_login_pinLen = 0; static int test_pkcs11_logouts = 0; static int test_pkcs11_closes = 0; static int test_pkcs11_opens = 0; @@ -1173,9 +1175,10 @@ static CK_RV test_pkcs11_login(CK_SESSION_HANDLE session, CK_USER_TYPE type, { (void)session; (void)type; - (void)pin; - (void)pinLen; + test_pkcs11_login_pinLen = pinLen; + if (pin != NULL && pinLen <= sizeof(test_pkcs11_login_pin)) + XMEMCPY(test_pkcs11_login_pin, pin, pinLen); test_pkcs11_logins++; return test_pkcs11_login_rv; } @@ -1196,6 +1199,32 @@ static CK_RV test_pkcs11_close_session(CK_SESSION_HANDLE session) return CKR_OK; } +static CK_RV test_pkcs11_slot_info(CK_SLOT_ID slotId, CK_SLOT_INFO_PTR info) +{ + (void)slotId; + + XMEMSET(info, 0, sizeof(*info)); + return CKR_OK; +} + +static CK_RV test_pkcs11_slot_list(CK_BBOOL present, CK_SLOT_ID_PTR list, + CK_ULONG_PTR cnt) +{ + (void)present; + + if (list != NULL) + list[0] = 1; + *cnt = 1; + return CKR_OK; +} + +static CK_RV test_pkcs11_close_all(CK_SLOT_ID slotId) +{ + (void)slotId; + + return CKR_OK; +} + #ifdef HAVE_ECC_SIGN static int test_pkcs11_sign_login(Pkcs11Token* token, ecc_key* key, WC_RNG* rng, CK_RV loginRv) @@ -1237,6 +1266,7 @@ int test_wc_Pkcs11_LoginShared(void) func.C_Login = test_pkcs11_login; func.C_Logout = test_pkcs11_logout; func.C_CloseSession = test_pkcs11_close_session; + func.C_CloseAllSessions = test_pkcs11_close_all; token.handle = 0; ExpectNotNull(token.userPin = (CK_UTF8CHAR_PTR)XMALLOC(4, NULL, DYNAMIC_TYPE_TMP_BUFFER)); @@ -1244,6 +1274,7 @@ int test_wc_Pkcs11_LoginShared(void) XMEMCPY(token.userPin, "1234", 4); token.userPinSz = 4; token.userPinLogin = 1; + token.pinOwned = 1; XMEMSET(&key, 0, sizeof(key)); ExpectIntEQ(wc_InitRng(&rng), 0); ExpectIntEQ(test_pkcs11_make_ecc(&key, &rng, id, (int)sizeof(id)), 0); @@ -1273,6 +1304,7 @@ int test_wc_Pkcs11_LoginShared(void) wc_Pkcs11Token_Close(&token); ExpectIntEQ(test_pkcs11_logouts, 0); + wc_Pkcs11Token_Final(&token); wc_ecc_free(&key); DoExpectIntEQ(wc_FreeRng(&rng), 0); @@ -1446,6 +1478,79 @@ int test_wc_Pkcs11_RsaKeyGenExponent(void) return EXPECT_RESULT(); } +int test_wc_Pkcs11_TokenPinCopy(void) +{ + static const unsigned char pin[] = "1234"; + unsigned char pin2[4] = { '5', '6', '7', '8' }; + Pkcs11Dev dev; + Pkcs11Token token; + CK_FUNCTION_LIST func; + EXPECT_DECLS; + + XMEMSET(&dev, 0, sizeof(dev)); + XMEMSET(&func, 0, sizeof(func)); + func.C_GetSlotInfo = test_pkcs11_slot_info; + func.C_CloseAllSessions = test_pkcs11_close_all; + func.C_OpenSession = test_pkcs11_open_session; + func.C_Login = test_pkcs11_login; + func.C_CloseSession = test_pkcs11_close_session; + dev.func = &func; + + /* A read-only PIN must not be written to on finalize. */ + ExpectIntEQ(wc_Pkcs11Token_Init(&token, &dev, 1, NULL, pin, 4), 0); + ExpectPtrNE(token.userPin, pin); + if (EXPECT_SUCCESS()) + ExpectBufEQ(token.userPin, pin, 4); + wc_Pkcs11Token_Final(&token); + ExpectNull(token.userPin); + ExpectBufEQ(pin, "1234", 4); + + ExpectIntEQ(wc_Pkcs11Token_Init(&token, &dev, 1, NULL, pin2, + (int)sizeof(pin2)), 0); + XMEMSET(pin2, 0, sizeof(pin2)); + if (EXPECT_SUCCESS()) + ExpectBufEQ(token.userPin, "5678", 4); + test_pkcs11_login_rv = CKR_OK; + ExpectIntEQ(wc_Pkcs11Token_Open(&token, 1), 0); + ExpectIntEQ(test_pkcs11_login_pinLen, 4); + ExpectBufEQ(test_pkcs11_login_pin, "5678", 4); + wc_Pkcs11Token_Close(&token); + wc_Pkcs11Token_Final(&token); + + ExpectIntEQ(wc_Pkcs11Token_Init(&token, &dev, 1, NULL, pin, 0), 0); + ExpectNotNull(token.userPin); + ExpectIntEQ(token.userPinSz, 0); + wc_Pkcs11Token_Final(&token); + + func.C_GetSlotList = test_pkcs11_slot_list; + ExpectIntEQ(wc_Pkcs11Token_InitName(&token, &dev, NULL, 0, pin, 4), 0); + ExpectPtrNE(token.userPin, pin); + if (EXPECT_SUCCESS()) + ExpectBufEQ(token.userPin, pin, 4); + wc_Pkcs11Token_Final(&token); + ExpectNull(token.userPin); + + /* A negative length is rejected before the token is set up. */ + XMEMSET(&token, 0, sizeof(token)); + ExpectIntEQ(wc_Pkcs11Token_Init(&token, &dev, 1, NULL, pin, -1), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectNull(token.func); + ExpectIntEQ(wc_Pkcs11Token_InitName(&token, &dev, "t", 1, pin, -1), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectNull(token.func); + + /* A PIN the application set itself is not freed. */ + ExpectIntEQ(wc_Pkcs11Token_Init_NoLogin(&token, &dev, 1, NULL), 0); + token.userPin = (CK_UTF8CHAR_PTR)pin; + token.userPinSz = 4; + token.userPinLogin = 1; + wc_Pkcs11Token_Final(&token); + ExpectNull(token.userPin); + ExpectBufEQ(pin, "1234", 4); + + return EXPECT_RESULT(); +} + int test_wc_Pkcs11_RsaKeyGenError(void) { EXPECT_DECLS; diff --git a/tests/api/test_pkcs11.h b/tests/api/test_pkcs11.h index 421ebf5ceb6..7a33f32703e 100644 --- a/tests/api/test_pkcs11.h +++ b/tests/api/test_pkcs11.h @@ -33,6 +33,7 @@ int test_wc_Pkcs11_FreeKeepsTokenKey(void); int test_wc_Pkcs11_RsaIdNoLeak(void); int test_wc_Pkcs11_EcKeyUsage(void); int test_wc_Pkcs11_LoginShared(void); +int test_wc_Pkcs11_TokenPinCopy(void); int test_wc_Pkcs11_RsaKeyGenError(void); int test_wc_Pkcs11_RsaKeyGenExponent(void); int test_wc_Pkcs11_FreeNoObject(void); @@ -44,6 +45,7 @@ int test_wc_Pkcs11_KeyGenSessionClosed(void); TEST_DECL_GROUP("pkcs11", test_wc_Pkcs11_RsaIdNoLeak), \ TEST_DECL_GROUP("pkcs11", test_wc_Pkcs11_EcKeyUsage), \ TEST_DECL_GROUP("pkcs11", test_wc_Pkcs11_LoginShared), \ + TEST_DECL_GROUP("pkcs11", test_wc_Pkcs11_TokenPinCopy), \ TEST_DECL_GROUP("pkcs11", test_wc_Pkcs11_RsaKeyGenError), \ TEST_DECL_GROUP("pkcs11", test_wc_Pkcs11_RsaKeyGenExponent), \ TEST_DECL_GROUP("pkcs11", test_wc_Pkcs11_FreeNoObject), \ diff --git a/wolfcrypt/src/wc_pkcs11.c b/wolfcrypt/src/wc_pkcs11.c index a1668a994b9..def608236b8 100644 --- a/wolfcrypt/src/wc_pkcs11.c +++ b/wolfcrypt/src/wc_pkcs11.c @@ -957,7 +957,9 @@ static int Pkcs11Token_Init(Pkcs11Token* token, Pkcs11Dev* dev, int slotId, token->userPin = NULL_PTR; token->userPinSz = 0; token->userPinLogin = 0; + token->pinOwned = 0; token->version = dev->version; + token->heap = dev->heap; } XFREE(slot, dev->heap, DYNAMIC_TYPE_TMP_BUFFER); @@ -965,6 +967,34 @@ static int Pkcs11Token_Init(Pkcs11Token* token, Pkcs11Dev* dev, int slotId, return ret; } +/** + * Keep a copy of the user's PIN in the token for logging in. + * + * @param [in] token Token object. + * @param [in] userPin PIN to use to login as user. + * @param [in] userPinSz Number of bytes in PIN. Not negative. + * @return MEMORY_E when dynamic memory allocation fails. + * @return 0 on success. + */ +static int Pkcs11Token_SetPin(Pkcs11Token* token, const unsigned char* userPin, + int userPinSz) +{ + int ret = 0; + + token->userPin = (CK_UTF8CHAR_PTR)XMALLOC(userPinSz > 0 ? userPinSz : 1, + token->heap, DYNAMIC_TYPE_TMP_BUFFER); + if (token->userPin == NULL) + ret = MEMORY_E; + if (ret == 0) { + XMEMCPY(token->userPin, userPin, (size_t)userPinSz); + token->userPinSz = (CK_ULONG)userPinSz; + token->userPinLogin = 1; + token->pinOwned = 1; + } + + return ret; +} + /** * Set up a token for use. Lookup by slotId or tokenName. Set User PIN. * @@ -976,6 +1006,8 @@ static int Pkcs11Token_Init(Pkcs11Token* token, Pkcs11Dev* dev, int slotId, * @param [in] userPin PIN to use to login as user. * @param [in] userPinSz Number of bytes in PIN. * @return BAD_FUNC_ARG when token, dev and/or tokenName is NULL. + * @return BAD_FUNC_ARG when userPinSz is negative. + * @return MEMORY_E when copying the PIN fails. * @return WC_INIT_E when initializing token fails. * @return WC_HW_E when another PKCS#11 library call fails. * @return 0 on success. @@ -989,12 +1021,12 @@ int wc_Pkcs11Token_Init(Pkcs11Token* token, Pkcs11Dev* dev, int slotId, if (tokenName != NULL) { tokenNameSz = XSTRLEN(tokenName); } - ret = Pkcs11Token_Init(token, dev, slotId, tokenName, tokenNameSz); - if (ret == 0 && userPin != NULL) { - token->userPin = (CK_UTF8CHAR_PTR)userPin; - token->userPinSz = (CK_ULONG)userPinSz; - token->userPinLogin = 1; - } + if (userPin != NULL && userPinSz < 0) + ret = BAD_FUNC_ARG; + else + ret = Pkcs11Token_Init(token, dev, slotId, tokenName, tokenNameSz); + if (ret == 0 && userPin != NULL) + ret = Pkcs11Token_SetPin(token, userPin, userPinSz); return ret; } @@ -1032,6 +1064,8 @@ int wc_Pkcs11Token_Init_NoLogin(Pkcs11Token* token, Pkcs11Dev* dev, int slotId, * @param [in] userPin PIN to use to login as user. * @param [in] userPinSz Number of bytes in PIN. * @return BAD_FUNC_ARG when token, dev and/or tokenName is NULL. + * @return BAD_FUNC_ARG when userPinSz is negative. + * @return MEMORY_E when copying the PIN fails. * @return WC_INIT_E when initializing token fails. * @return WC_HW_E when another PKCS#11 library call fails. * @return 0 on success. @@ -1040,12 +1074,14 @@ int wc_Pkcs11Token_InitName(Pkcs11Token* token, Pkcs11Dev* dev, const char* tokenName, int tokenNameSz, const unsigned char* userPin, int userPinSz) { - int ret = Pkcs11Token_Init(token, dev, -1, tokenName, (size_t)tokenNameSz); - if (ret == 0 && userPin != NULL) { - token->userPin = (CK_UTF8CHAR_PTR)userPin; - token->userPinSz = (CK_ULONG)userPinSz; - token->userPinLogin = 1; - } + int ret; + + if (userPin != NULL && userPinSz < 0) + ret = BAD_FUNC_ARG; + else + ret = Pkcs11Token_Init(token, dev, -1, tokenName, (size_t)tokenNameSz); + if (ret == 0 && userPin != NULL) + ret = Pkcs11Token_SetPin(token, userPin, userPinSz); return ret; } @@ -1070,6 +1106,24 @@ int wc_Pkcs11Token_InitName_NoLogin(Pkcs11Token* token, Pkcs11Dev* dev, return Pkcs11Token_Init(token, dev, -1, tokenName, (size_t)tokenNameSz); } +/** + * Zeroize and free the token's copy of the user's PIN. + * A PIN the application set in the token itself is only forgotten. + * + * @param [in] token Token object. + */ +static void Pkcs11Token_ClearPin(Pkcs11Token* token) +{ + if (token->userPin != NULL && token->pinOwned) { + ForceZero(token->userPin, (word32)token->userPinSz); + XFREE(token->userPin, token->heap, DYNAMIC_TYPE_TMP_BUFFER); + } + token->userPin = NULL_PTR; + token->userPinSz = 0; + token->userPinLogin = 0; + token->pinOwned = 0; +} + /** * Finalize token. * Closes all sessions on token. @@ -1081,7 +1135,7 @@ void wc_Pkcs11Token_Final(Pkcs11Token* token) if (token != NULL && token->func != NULL) { token->func->C_CloseAllSessions(token->slotId); token->handle = NULL_PTR; - ForceZero(token->userPin, (word32)token->userPinSz); + Pkcs11Token_ClearPin(token); } } diff --git a/wolfssl/wolfcrypt/wc_pkcs11.h b/wolfssl/wolfcrypt/wc_pkcs11.h index 0477666edbd..aa612dd32f6 100644 --- a/wolfssl/wolfcrypt/wc_pkcs11.h +++ b/wolfssl/wolfcrypt/wc_pkcs11.h @@ -60,8 +60,10 @@ typedef struct Pkcs11Token { CK_SESSION_HANDLE handle; /* Handle to active session */ CK_UTF8CHAR_PTR userPin; /* User's PIN to login with */ CK_ULONG userPinSz; /* Size of user's PIN in bytes */ - byte userPinLogin:1; /* Login with User's PIN */ + WC_BITFIELD userPinLogin:1; /* Login with User's PIN */ + WC_BITFIELD pinOwned:1; /* userPin is the token's copy */ int version; /* Pkcs11InterfaceVersionType */ + void* heap; /* Heap for the copy of the PIN */ } Pkcs11Token; typedef struct Pkcs11Session { From abdc5f5facc4739da42c0da299f7134dbf49024f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Tue, 10 Nov 2026 19:17:56 +0100 Subject: [PATCH 3/3] Let wc_Pkcs11Token_Open_ex discard the PKCS#11 PIN The token keeps its copy of the user PIN for its whole lifetime. PKCS#11 keeps the login state per application for as long as one of its sessions is open, and every operation runs on the session that wc_Pkcs11Token_Open opened. Once that session is logged in, an application may not want the PIN kept in memory. wc_Pkcs11Token_Open_ex takes a discardPin flag. With it set, the PIN copy is zeroized and freed once the call has opened the session and logged it in, whether its own C_Login succeeded or reported CKR_USER_ALREADY_LOGGED_IN because another token of the application logged in first. Either way the login lasts while a session of the application is open, since nothing calls C_Logout any more. A pinDropped bit in Pkcs11Token then makes another open return BAD_STATE_E, rather than a session that is not logged in, until the token is finalized and initialized again with the PIN. Operations that open their own session, as after wc_Pkcs11Token_Close, still do so but skip C_Login. The application stays logged in while any of its sessions is open, as wolfPKCS11's per-call session interop test relies on, and with none open their private key operations fail with WC_HW_E. wc_Pkcs11Token_Final clears the bit along with the PIN. A session that was already open, possibly one the application set up itself, keeps the PIN, as wolfSSL did not log it in. wc_Pkcs11Token_Open keeps the PIN, so a token can still be closed and opened again, and operations after wc_Pkcs11Token_Close log in on their own session as before. wc_Pkcs11Token_Open also stored the session handle when opening or logging in failed. A failed C_Login left the session open, and a retry reused it without logging in. The session is now closed when its login fails and the handle is stored only on success. Calling the function again while a session is open does nothing, and the session keeps its read/write mode. --- doc/dox_comments/header_files/pkcs11.h | 41 +++++++- tests/api/test_pkcs11.c | 140 ++++++++++++++++++++++++- tests/api/test_pkcs11.h | 2 + wolfcrypt/src/wc_pkcs11.c | 38 ++++++- wolfssl/wolfcrypt/wc_pkcs11.h | 3 + 5 files changed, 214 insertions(+), 10 deletions(-) diff --git a/doc/dox_comments/header_files/pkcs11.h b/doc/dox_comments/header_files/pkcs11.h index 96f20f9f24d..2eeb29ebdba 100644 --- a/doc/dox_comments/header_files/pkcs11.h +++ b/doc/dox_comments/header_files/pkcs11.h @@ -13,8 +13,7 @@ void wc_Pkcs11_Finalize(Pkcs11Dev* dev); \ingroup PKCS11 \brief Sets up a token by slot id or name. A user PIN is copied into - memory the token owns. Call wc_Pkcs11Token_Final before initializing the - same token again, so that the copy is zeroized and freed. + memory the token owns, which wc_Pkcs11Token_Final zeroizes and frees. */ int wc_Pkcs11Token_Init(Pkcs11Token* token, Pkcs11Dev* dev, int slotId, const char* tokenName, const unsigned char *userPin, @@ -43,17 +42,53 @@ void wc_Pkcs11Token_Final(Pkcs11Token* token); with an id or label on the token return BAD_STATE_E, and a key with neither is generated in software. + Calling this again while the session is open does nothing and keeps the + session's read/write mode. + \return 0 on success. \return BAD_FUNC_ARG when token is NULL. - \return WC_HW_E when opening the session fails. + \return BAD_STATE_E when wc_Pkcs11Token_Open_ex discarded the PIN and its + session was closed. + \return WC_HW_E when opening the session or logging in fails. A session + whose login failed is closed again. \param token Token to open the session on. \param readWrite 1 for a read-write session, 0 for read-only. + \sa wc_Pkcs11Token_Open_ex \sa wc_Pkcs11Token_Close */ int wc_Pkcs11Token_Open(Pkcs11Token* token, int readWrite); +/*! + \ingroup PKCS11 + + \brief Opens a session as wc_Pkcs11Token_Open does. With discardPin set, + the token's copy of the user PIN is zeroized and freed once this call has + opened the session and logged it in, by its own login or because another + token already logged the application in. A session that was already open + keeps the PIN. The login then lasts while a session of the + application is open. After wc_Pkcs11Token_Close, opening again returns + BAD_STATE_E until wc_Pkcs11Token_Final and a new init with the PIN, and + operations open sessions without logging in. Discarding the PIN is not + safe while other threads use the token. + + \return 0 on success. + \return BAD_FUNC_ARG when token is NULL. + \return BAD_STATE_E when an earlier open discarded the PIN and its session + was closed. + \return WC_HW_E when opening the session or logging in fails. A session + whose login failed is closed again. + + \param token Token to open the session on. + \param readWrite 1 for a read-write session, 0 for read-only. + \param discardPin 1 to discard the PIN once the session is logged in. + + \sa wc_Pkcs11Token_Open + \sa wc_Pkcs11Token_Final + */ +int wc_Pkcs11Token_Open_ex(Pkcs11Token* token, int readWrite, int discardPin); + /*! \ingroup PKCS11 diff --git a/tests/api/test_pkcs11.c b/tests/api/test_pkcs11.c index 3f936e6e9f5..79f89f3d51d 100644 --- a/tests/api/test_pkcs11.c +++ b/tests/api/test_pkcs11.c @@ -1170,6 +1170,18 @@ static CK_RV test_pkcs11_open_session(CK_SLOT_ID slotId, CK_FLAGS flags, return CKR_OK; } +static CK_RV test_pkcs11_open_session_fail(CK_SLOT_ID slotId, CK_FLAGS flags, + CK_VOID_PTR app, CK_NOTIFY notify, CK_SESSION_HANDLE_PTR session) +{ + (void)slotId; + (void)flags; + (void)app; + (void)notify; + + *session = 7; + return TEST_CKR_GENERAL_ERROR; +} + static CK_RV test_pkcs11_login(CK_SESSION_HANDLE session, CK_USER_TYPE type, CK_UTF8CHAR_PTR pin, CK_ULONG pinLen) { @@ -1291,6 +1303,7 @@ int test_wc_Pkcs11_LoginShared(void) ExpectIntEQ(test_pkcs11_sign_login(&token, &key, &rng, TEST_CKR_GENERAL_ERROR), WC_NO_ERR_TRACE(WC_HW_E)); + ExpectIntEQ(test_pkcs11_closes, 1); #endif test_pkcs11_login_rv = CKR_USER_ALREADY_LOGGED_IN; @@ -1299,10 +1312,22 @@ int test_wc_Pkcs11_LoginShared(void) wc_Pkcs11Token_Close(&token); ExpectIntEQ(test_pkcs11_logouts, 0); - test_pkcs11_login_rv = CKR_OK; - ExpectIntEQ(wc_Pkcs11Token_Open(&token, 1), 0); +#ifdef HAVE_ECC_SIGN + test_pkcs11_logins = 0; + ExpectIntEQ(test_pkcs11_sign_login(&token, &key, &rng, CKR_OK), 0); + ExpectIntEQ(test_pkcs11_logins, 1); +#endif + + ExpectIntEQ(wc_Pkcs11Token_Open_ex(&token, 1, 1), 0); wc_Pkcs11Token_Close(&token); - ExpectIntEQ(test_pkcs11_logouts, 0); + +#ifdef HAVE_ECC_SIGN + /* Without the PIN an operation's session does not log in. */ + test_pkcs11_logins = 0; + ExpectIntEQ(test_pkcs11_sign_login(&token, &key, &rng, CKR_OK), 0); + ExpectIntEQ(test_pkcs11_logins, 0); + ExpectIntEQ(test_pkcs11_closes, 1); +#endif wc_Pkcs11Token_Final(&token); wc_ecc_free(&key); @@ -1633,4 +1658,113 @@ int test_wc_Pkcs11_RsaKeyGenError(void) return EXPECT_RESULT(); } +int test_wc_Pkcs11_TokenOpenDropsPin(void) +{ + static const unsigned char pin[] = "1234"; + Pkcs11Dev dev; + Pkcs11Token token; + CK_FUNCTION_LIST func; + EXPECT_DECLS; + + XMEMSET(&dev, 0, sizeof(dev)); + XMEMSET(&func, 0, sizeof(func)); + func.C_GetSlotInfo = test_pkcs11_slot_info; + func.C_CloseAllSessions = test_pkcs11_close_all; + func.C_OpenSession = test_pkcs11_open_session; + func.C_Login = test_pkcs11_login; + func.C_Logout = test_pkcs11_logout; + func.C_CloseSession = test_pkcs11_close_session; + dev.func = &func; + ExpectIntEQ(wc_Pkcs11Token_Init(&token, &dev, 1, NULL, pin, 4), 0); + + /* wc_Pkcs11Token_Open keeps the PIN, so the token can be opened again. */ + test_pkcs11_login_rv = CKR_OK; + test_pkcs11_logins = 0; + ExpectIntEQ(wc_Pkcs11Token_Open(&token, 1), 0); + ExpectNotNull(token.userPin); + ExpectIntEQ(token.pinDropped, 0); + wc_Pkcs11Token_Close(&token); + ExpectIntEQ(wc_Pkcs11Token_Open(&token, 1), 0); + ExpectIntEQ(test_pkcs11_logins, 2); + /* A session that is already open keeps the PIN. */ + ExpectIntEQ(wc_Pkcs11Token_Open_ex(&token, 1, 1), 0); + ExpectNotNull(token.userPin); + ExpectIntEQ(token.pinDropped, 0); + wc_Pkcs11Token_Close(&token); + + /* A login shared with another token also lasts while the session is + * open. */ + test_pkcs11_login_rv = CKR_USER_ALREADY_LOGGED_IN; + ExpectIntEQ(wc_Pkcs11Token_Open_ex(&token, 1, 1), 0); + ExpectNull(token.userPin); + ExpectIntEQ(token.pinDropped, 1); + wc_Pkcs11Token_Close(&token); + wc_Pkcs11Token_Final(&token); + ExpectIntEQ(wc_Pkcs11Token_Init(&token, &dev, 1, NULL, pin, 4), 0); + + test_pkcs11_login_rv = CKR_OK; + test_pkcs11_logins = 0; + test_pkcs11_logouts = 0; + ExpectIntEQ(wc_Pkcs11Token_Open_ex(&token, 1, 1), 0); + ExpectIntEQ(test_pkcs11_logins, 1); + ExpectNull(token.userPin); + ExpectIntEQ(token.userPinSz, 0); + ExpectIntEQ(token.userPinLogin, 0); + wc_Pkcs11Token_Close(&token); + ExpectIntEQ(test_pkcs11_logouts, 0); + + /* Without the PIN no session can be opened until the token is set up + * again. */ + ExpectIntEQ(wc_Pkcs11Token_Open_ex(&token, 1, 1), + WC_NO_ERR_TRACE(BAD_STATE_E)); + ExpectIntEQ(test_pkcs11_logins, 1); + ExpectIntEQ(token.handle, 0); + wc_Pkcs11Token_Final(&token); + ExpectIntEQ(token.pinDropped, 0); + + /* A failed login closes the session and keeps no handle. */ + ExpectIntEQ(wc_Pkcs11Token_Init(&token, &dev, 1, NULL, pin, 4), 0); + test_pkcs11_login_rv = TEST_CKR_GENERAL_ERROR; + test_pkcs11_closes = 0; + ExpectIntEQ(wc_Pkcs11Token_Open_ex(&token, 1, 1), + WC_NO_ERR_TRACE(WC_HW_E)); + ExpectIntEQ(token.handle, 0); + ExpectIntEQ(test_pkcs11_closes, 1); + ExpectNotNull(token.userPin); + + /* A failed C_OpenSession keeps no handle and no login. */ + func.C_OpenSession = test_pkcs11_open_session_fail; + test_pkcs11_logins = 0; + ExpectIntEQ(wc_Pkcs11Token_Open_ex(&token, 1, 1), + WC_NO_ERR_TRACE(WC_HW_E)); + ExpectIntEQ(token.handle, 0); + ExpectIntEQ(test_pkcs11_logins, 0); + ExpectNotNull(token.userPin); + ExpectIntEQ(token.pinDropped, 0); + func.C_OpenSession = test_pkcs11_open_session; + + /* A second open keeps the first session and its login. */ + test_pkcs11_login_rv = CKR_OK; + test_pkcs11_logins = 0; + ExpectIntEQ(wc_Pkcs11Token_Open_ex(&token, 1, 1), 0); + ExpectIntEQ(token.pinDropped, 1); + ExpectIntEQ(wc_Pkcs11Token_Open_ex(&token, 1, 1), 0); + ExpectIntEQ(test_pkcs11_logins, 1); + wc_Pkcs11Token_Close(&token); + wc_Pkcs11Token_Final(&token); + + /* A token without a PIN has nothing to discard and can be reopened. */ + ExpectIntEQ(wc_Pkcs11Token_Init_NoLogin(&token, &dev, 1, NULL), 0); + test_pkcs11_logins = 0; + ExpectIntEQ(wc_Pkcs11Token_Open_ex(&token, 1, 1), 0); + ExpectIntEQ(token.pinDropped, 0); + wc_Pkcs11Token_Close(&token); + ExpectIntEQ(wc_Pkcs11Token_Open_ex(&token, 1, 1), 0); + ExpectIntEQ(test_pkcs11_logins, 0); + wc_Pkcs11Token_Close(&token); + wc_Pkcs11Token_Final(&token); + + return EXPECT_RESULT(); +} + #endif diff --git a/tests/api/test_pkcs11.h b/tests/api/test_pkcs11.h index 7a33f32703e..2725429d9ce 100644 --- a/tests/api/test_pkcs11.h +++ b/tests/api/test_pkcs11.h @@ -36,6 +36,7 @@ int test_wc_Pkcs11_LoginShared(void); int test_wc_Pkcs11_TokenPinCopy(void); int test_wc_Pkcs11_RsaKeyGenError(void); int test_wc_Pkcs11_RsaKeyGenExponent(void); +int test_wc_Pkcs11_TokenOpenDropsPin(void); int test_wc_Pkcs11_FreeNoObject(void); int test_wc_Pkcs11_KeyGenSessionClosed(void); @@ -48,6 +49,7 @@ int test_wc_Pkcs11_KeyGenSessionClosed(void); TEST_DECL_GROUP("pkcs11", test_wc_Pkcs11_TokenPinCopy), \ TEST_DECL_GROUP("pkcs11", test_wc_Pkcs11_RsaKeyGenError), \ TEST_DECL_GROUP("pkcs11", test_wc_Pkcs11_RsaKeyGenExponent), \ + TEST_DECL_GROUP("pkcs11", test_wc_Pkcs11_TokenOpenDropsPin), \ TEST_DECL_GROUP("pkcs11", test_wc_Pkcs11_FreeNoObject), \ TEST_DECL_GROUP("pkcs11", test_wc_Pkcs11_KeyGenSessionClosed) diff --git a/wolfcrypt/src/wc_pkcs11.c b/wolfcrypt/src/wc_pkcs11.c index def608236b8..1406dddcd8c 100644 --- a/wolfcrypt/src/wc_pkcs11.c +++ b/wolfcrypt/src/wc_pkcs11.c @@ -958,6 +958,7 @@ static int Pkcs11Token_Init(Pkcs11Token* token, Pkcs11Dev* dev, int slotId, token->userPinSz = 0; token->userPinLogin = 0; token->pinOwned = 0; + token->pinDropped = 0; token->version = dev->version; token->heap = dev->heap; } @@ -1136,6 +1137,7 @@ void wc_Pkcs11Token_Final(Pkcs11Token* token) token->func->C_CloseAllSessions(token->slotId); token->handle = NULL_PTR; Pkcs11Token_ClearPin(token); + token->pinDropped = 0; } } @@ -1181,6 +1183,7 @@ static int Pkcs11OpenSession(Pkcs11Token* token, Pkcs11Session* session, PKCS11_RV("C_Login", rv); /* Another token in this process may have logged in already. */ if (rv != CKR_OK && rv != CKR_USER_ALREADY_LOGGED_IN) { + token->func->C_CloseSession(session->handle); ret = WC_HW_E; } } @@ -1242,10 +1245,29 @@ static int Pkcs11KeyGenSessionCheck(Pkcs11Session* session, int idLen, * @param [in] token Token object. * @param [in] readWrite Boolean indicating to open session for Read/Write. * @return BAD_FUNC_ARG when token is NULL. - * @return WC_HW_E when opening the session fails. - * @return 0 on success. + * @return BAD_STATE_E when the PIN was discarded by an earlier open. + * @return WC_HW_E when opening the session or logging in fails. + * @return 0 on success, or when a session is already open. */ int wc_Pkcs11Token_Open(Pkcs11Token* token, int readWrite) +{ + return wc_Pkcs11Token_Open_ex(token, readWrite, 0); +} + +/** + * Open a session on the token to be used for all operations. + * Optionally discard the copy of the user's PIN once this call has opened and + * logged in the session. + * + * @param [in] token Token object. + * @param [in] readWrite Boolean indicating to open session for Read/Write. + * @param [in] discardPin Boolean indicating to discard the user's PIN. + * @return BAD_FUNC_ARG when token is NULL. + * @return BAD_STATE_E when the PIN was discarded by an earlier open. + * @return WC_HW_E when opening the session or logging in fails. + * @return 0 on success, or when a session is already open. + */ +int wc_Pkcs11Token_Open_ex(Pkcs11Token* token, int readWrite, int discardPin) { int ret = 0; Pkcs11Session session; @@ -1253,9 +1275,17 @@ int wc_Pkcs11Token_Open(Pkcs11Token* token, int readWrite) if (token == NULL) ret = BAD_FUNC_ARG; - if (ret == 0) { + if (ret == 0 && token->handle == NULL_PTR && token->pinDropped) + ret = BAD_STATE_E; + else if (ret == 0 && token->handle == NULL_PTR) { ret = Pkcs11OpenSession(token, &session, readWrite); - token->handle = session.handle; + if (ret == 0) + token->handle = session.handle; + /* The login lasts while this session is open. */ + if (ret == 0 && discardPin && token->userPinLogin) { + Pkcs11Token_ClearPin(token); + token->pinDropped = 1; + } } return ret; diff --git a/wolfssl/wolfcrypt/wc_pkcs11.h b/wolfssl/wolfcrypt/wc_pkcs11.h index aa612dd32f6..406458dc338 100644 --- a/wolfssl/wolfcrypt/wc_pkcs11.h +++ b/wolfssl/wolfcrypt/wc_pkcs11.h @@ -62,6 +62,7 @@ typedef struct Pkcs11Token { CK_ULONG userPinSz; /* Size of user's PIN in bytes */ WC_BITFIELD userPinLogin:1; /* Login with User's PIN */ WC_BITFIELD pinOwned:1; /* userPin is the token's copy */ + WC_BITFIELD pinDropped:1; /* PIN discarded after logging in */ int version; /* Pkcs11InterfaceVersionType */ void* heap; /* Heap for the copy of the PIN */ } Pkcs11Token; @@ -106,6 +107,8 @@ WOLFSSL_API int wc_Pkcs11Token_InitName_NoLogin(Pkcs11Token* token, WOLFSSL_API void wc_Pkcs11Token_Final(Pkcs11Token* token); WOLFSSL_API int wc_Pkcs11Token_Open(Pkcs11Token* token, int readWrite); +WOLFSSL_API int wc_Pkcs11Token_Open_ex(Pkcs11Token* token, int readWrite, + int discardPin); WOLFSSL_API void wc_Pkcs11Token_Close(Pkcs11Token* token); WOLFSSL_API int wc_Pkcs11StoreKey(Pkcs11Token* token, int type, int clear,