Skip to content

Share the PKCS#11 login between tokens and copy the user PIN - #12111

Open
Frauschi wants to merge 3 commits into
wolfSSL:masterfrom
Frauschi:pkcs11-login
Open

Frauschi wants to merge 3 commits into
wolfSSL:masterfrom
Frauschi:pkcs11-login

Conversation

@Frauschi

Copy link
Copy Markdown
Member

Description

This PR lets several Pkcs11Tokens of one application use the same slot without logging each other out, and stops the token from keeping a pointer to the caller's PIN.

  • Sessions no longer call C_Logout, and CKR_USER_ALREADY_LOGGED_IN counts as a successful login, so closing one token's session leaves another's login in place.
  • wc_Pkcs11Token_Init and wc_Pkcs11Token_InitName copy the PIN into token-owned heap memory, which wc_Pkcs11Token_Final zeroizes and frees; a PIN in read-only memory no longer crashes on finalize.
  • A negative PIN length is rejected before any PKCS#11 call.
  • New wc_Pkcs11Token_Open_ex(token, readWrite, discardPin) can discard the PIN once its session is logged in. wc_Pkcs11Token_Open keeps the PIN, so close and reopen work as before.
  • A session whose C_Login fails is closed, and its handle is not kept.

Behavior changes

  • wc_Pkcs11Token_Close and per-operation sessions no longer log out.
  • wc_Pkcs11Token_Final no longer zeroizes the caller's PIN buffer.
  • Pkcs11Token gains heap, pinOwned and pinDropped.

Testing

  • test_wc_Pkcs11_LoginShared: shared login, failed login closing its session, operations after close with and without the PIN.
  • test_wc_Pkcs11_TokenPinCopy: PIN copy, empty and negative lengths, a PIN the application assigned itself.
  • test_wc_Pkcs11_TokenOpenDropsPin: Open versus Open_ex with discardPin, reopen, failed login, already open session.

Merging

Adds tests next to #11876 in tests/api/test_pkcs11.c; whichever merges second needs a small rebase there.

Copilot AI balanced review requested due to automatic review settings November 12, 2026 12:30
@Frauschi Frauschi self-assigned this Nov 12, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The ECDSA test helper and assertions lack PKCS#11 ECC guards, causing return-value and session-count assertions to fail when NO_PKCS11_ECC is defined but software ECC signing remains enabled.

1 open issue
What changed in this PR

This PR updates wolfSSL’s PKCS#11 integration to preserve shared logins across session closes and store token-owned copies of user PINs.

Changes:

  • Accepts existing logins, avoids session logout, and closes sessions when login fails.
  • Copies and securely clears PINs, with a new Open_ex option to discard them after login.
  • Adds API documentation and tests for login sharing, PIN ownership, and session lifecycle.
File Description
wolfssl/​wolfcrypt/​wc_pkcs11.h Adds PIN ownership state and declares Open_ex.
wolfssl/​wolfcrypt/​pkcs11.h Defines the already-logged-in return code.
wolfcrypt/​src/​wc_pkcs11.c Implements shared-login handling, PIN ownership, and session cleanup.
tests/​api/​test_pkcs11.h Declares and registers the new tests.
tests/​api/​test_pkcs11.c Tests login sharing, PIN copying, and optional PIN disposal.
doc/​dox_comments/​header_files/​pkcs11.h Documents PIN ownership and session behavior.

Review effort: Balanced

Comment thread tests/api/test_pkcs11.c Resolved
@github-actions

github-actions Bot commented Nov 12, 2026 •

Copy link
Copy Markdown

MemBrowse Memory Report · Explore what changed →

zephyr-frdm_rw612

  • FLASH: datas +16 B, log_const_area -8 B, rodata +36 B, text +11,680 B (+4.2%, 294,188 B / 67,108,864 B, total: 0% used)
  • RAM: bss +49 B, datas +16 B (+0.1%, 58,423 B / 983,040 B, total: 6% used)

No memory changes detected for:


Catch memory regressions in CI with MemBrowse. Free for open source.

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.
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.
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.
@Frauschi

Copy link
Copy Markdown
Member Author

@claude review

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Findings marked 🟡 are optional suggestions and need no follow-up push.

Comment thread wolfcrypt/src/wc_pkcs11.c
Comment on lines +1116 to +1121
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);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Applications that init a token with a PIN and later point userPin at their own buffer now get that buffer freed by Final, while the copy leaks. Pkcs11Token_ClearPin frees token->userPin whenever pinOwned is set. The flag stays set after Init even if the public userPin field is reassigned. Fix: free only the buffer the token allocated, e.g. keep the owned copy in its own field and free that, never whatever userPin points to. A pointer the application assigns would then only be forgotten, as the PR's NoLogin test expects.

Why this was flagged

This assumes an application calls wc_Pkcs11Token_Init or wc_Pkcs11Token_InitName with a PIN and later assigns token.userPin/userPinSz itself, for example after changing the PIN. userPin is a public field in wolfssl/wolfcrypt/wc_pkcs11.h, and no in-repo caller rules this out. Pkcs11Token_SetPin (wc_pkcs11.c:985-993) sets pinOwned = 1, and nothing clears it when the field is reassigned. wc_Pkcs11Token_Final then calls Pkcs11Token_ClearPin (wc_pkcs11.c:1118-1120), which runs ForceZero and XFREE on the application's pointer. A string literal or static buffer crashes. A heap buffer is double-freed when the application frees it later. The token's own PIN copy leaks without being zeroized. On the base branch Final only ran ForceZero on whatever userPin pointed to and freed nothing. The PR's 'PIN the application set itself' test (test_pkcs11.c:1566-1573) covers only Init_NoLogin, where pinOwned is 0.

Verification: Pkcs11Token_SetPin (wc_pkcs11.c:985-993) sets token->pinOwned = 1. No code clears pinOwned when the public field is reassigned. Pkcs11Token_ClearPin (wc_pkcs11.c:1118-1120) then frees the application's buffer from wc_Pkcs11Token_Final, and the token's copy leaks. On the base, Final never freed it. tests/api/test_pkcs11.c:1567-1574 covers only the Init_NoLogin path.

Comment thread wolfcrypt/src/wc_pkcs11.c
Comment on lines +1025 to +1030
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);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 (optional) Applications that call Init again on a token that still holds a PIN copy now leak the old copy without zeroizing it. One example is retrying after a wrong PIN without calling Final. Pkcs11Token_Init resets userPin to NULL before Pkcs11Token_SetPin allocates a new copy, so the previous owned buffer is lost. Fix: document that wc_Pkcs11Token_Final must come before any re-init, since Init cannot tell an initialized token from stack garbage. Otherwise give callers a way to change the PIN that clears the old copy first.

Why this was flagged

This assumes an external application re-initializes a token with wc_Pkcs11Token_Init or wc_Pkcs11Token_InitName without calling wc_Pkcs11Token_Final first. Retrying login with a corrected PIN after WC_HW_E is one case. The only in-repo callers are tests, so the outside population cannot be ruled out. Pkcs11Token_Init overwrites token->userPin = NULL_PTR and pinOwned = 0 (wc_pkcs11.c:957-961). Pkcs11Token_SetPin then XMALLOCs a fresh copy (wc_pkcs11.c:985), so the earlier copy is never freed or wiped. Each retry leaves a plaintext PIN in leaked heap memory. On the base branch re-init only replaced a pointer to the caller's own buffer, so nothing was allocated and nothing leaked. No safeguard applies: Init has no way to free the previous copy, and the docs (doc/dox_comments/header_files/pkcs11.h:14-16) say only that Final frees the copy.

Verification: Pkcs11Token_Init (wolfcrypt/src/wc_pkcs11.c:957-963) writes token->userPin = NULL_PTR; without looking at the old values. Pkcs11Token_SetPin then does token->userPin = (CK_UTF8CHAR_PTR)XMALLOC(...), so the buffer allocated by the earlier Init is never freed, and its PIN bytes are never wiped. On the base, Init only stored the caller's pointer, so re-initializing without Final leaked nothing.

Comment thread wolfcrypt/src/wc_pkcs11.c
Comment on lines 1136 to 1137
if (token != NULL && token->func != NULL) {
token->func->C_CloseAllSessions(token->slotId);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟣 pre-existing, not blocking: pre-existing: Finalizing one token still kills every other token's session on the same slot, so the PR's shared-login goal breaks on Final. wc_Pkcs11Token_Final calls C_CloseAllSessions(token->slotId) (wc_pkcs11.c:1137). That closes all of the application's sessions on the slot, including another token's open handle, and drops the shared login. Fix: in Final, close only this token's own session (as wc_Pkcs11Token_Close does via Pkcs11CloseSession) rather than every session on the slot, so other tokens keep their sessions and login.
A small fix can ride a push you are already making; otherwise a short reply is enough.

Why this was flagged

Two tokens A and B are initialised on the same slot. B calls wc_Pkcs11Token_Open and holds a logged-in session in B.handle. A then calls wc_Pkcs11Token_Final. At wc_pkcs11.c:1137 that calls token->func->C_CloseAllSessions(token->slotId), which by the PKCS#11 spec closes every session the application has on that slot. B.handle is left pointing at a closed session. B's next operation reuses it through Pkcs11OpenSession (token->handle != NULL_PTR) and gets CKR_SESSION_HANDLE_INVALID, which surfaces as WC_HW_E. Closing the last session also logs the application out. The base branch behaves the same way. But this PR removes C_Logout from Pkcs11CloseSession and accepts CKR_USER_ALREADY_LOGGED_IN specifically so that tokens sharing a slot do not disrupt each other, and Final is the remaining path that still does. The diff edits Final itself but keeps this call.

Verification: Triggers when two Pkcs11Token objects on the same slot are in use and one is finalized while the other still holds an open session. wc_Pkcs11Token_Final (wc_pkcs11.c:1134-1141) still calls C_CloseAllSessions at line 1137. Under PKCS#11, C_CloseAllSessions closes every session the application has on that slot. The base already fails this way, so this is pre-existing, not introduced.

@wolfSSL-Fenrir-bot wolfSSL-Fenrir-bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fenrir Automated Review — PR #12111

Scan targets checked: wolfcrypt-src, wolfcrypt-bugs, wolfssl-src, wolfssl-bugs
Not reviewed (no scan target covers them): doc/dox_comments/header_files/pkcs11.h, tests/api/test_pkcs11.h
Coverage: 3 of 4 in-scope changed file(s) opened by the reviewer; not opened: wolfssl/wolfcrypt/pkcs11.h

Fenrir result: Approved ✅

No new issues found in the changed files.

Advisory only — this automated result does not count as a GitHub approval.

Review tier: Max

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants