Skip to content

Add opt-in no_login_cache to release the token login session on key free.#657

Open
dantecmelo wants to merge 4 commits into
OpenSC:masterfrom
dantecmelo:session-fix
Open

Add opt-in no_login_cache to release the token login session on key free.#657
dantecmelo wants to merge 4 commits into
OpenSC:masterfrom
dantecmelo:session-fix

Conversation

@dantecmelo

Copy link
Copy Markdown

Pull Request Type

  • Bug fix
  • New feature
  • Code style / formatting / renaming
  • Refactoring (no functional or API changes)
  • Build / CI related changes
  • Documentation
  • Other (please describe):

Related Issue

Issue number: N/A

Current Behavior

libp11 caches the token login session for the lifetime of the process and would
normally release it at exit. However, its exit-time teardown is deliberately
skipped: the atexit handler in util_uri.c sets g_shutdown_mode, which makes
UTIL_CTX_free_libp11 skip PKCS11_release_all_slots / C_Finalize (to avoid
calling into the module during OpenSSL's teardown). As a result the login session
is never released.

On a token with a hard limit on concurrent authenticated sessions — notably the
YubiHSM 2 (max 16) — running one short-lived openssl process per file leaks one
session per process (on the YubiHSM, C_CloseSession does not free the
authenticated session; only C_Logout / C_Finalize does). After 16 files the
17th fails with CKR_SESSION_COUNT ("could not read private key").

New Behavior

Adds an opt-in no_login_cache provider parameter (env PKCS11_NO_LOGIN_CACHE)
and a matching public PKCS11_set_no_login_cache() API. When enabled, the token
is logged out and its sessions are closed as soon as a private key object is
freed — during normal runtime instead of at the skipped exit. Re-login happens
automatically on the next key load. With the parameter unset, behaviour is
byte-for-byte unchanged.

Scope of Changes

  • Core: a self-contained, session-only logout helper
    pkcs11_slot_logout_session_only() (p11_slot.c), a per-context/per-slot
    no_login_cache flag, and the public PKCS11_set_no_login_cache()
    (p11_front.c, libp11.h, libp11.exports).
  • Provider front-end: a no_login_cache parameter mirroring force_login
    (provider_helpers.c, util_uri.c, util.h). Because the provider's
    keymgmt_load transfers ownership of the key to OpenSSL, the wrapped key's
    legacy RSA/EC finish callback does not run during the process — so the logout
    is triggered from keymgmt_free (provider.c) via p11_keydata_release_login(),
    which reaches the slot through the wrapped key's RSA/EC ex-data
    (pkcs11_release_login_for_pkey() in p11_key.c).
  • Engine front-end: the same release is triggered from the RSA/EC key finish
    callbacks (p11_rsa.c, p11_ec.c), where keys are freed via that path.

Testing

  • Existing tests
  • New tests added
  • Manual testing

Verified on a YubiHSM 2 (max 16 sessions) with the OpenSSL 3 provider on Debian:
a 50-iteration loop of openssl dgst -sha512 -sign, one process per file,
previously failed at the 17th with CKR_SESSION_COUNT; with no_login_cache = 1
it now completes all 50. A PKCS#11 trace shows one C_Logout per key free, and
the device's active session count returns to baseline between runs.

Additional Notes

  • Opt-in and backward compatible: with the parameter unset, behaviour is
    byte-for-byte unchanged.
  • Safety / re-entrancy: the logout helper is self-contained — it does not call
    pkcs11_wipe_cache or pkcs11_get_session, so it cannot re-enter the key-free
    callback or nest slot->lock; it is idempotent via a lockless logged_in check.
  • Key-type scope: covers RSA and ECDSA (EC). EdDSA (Ed25519/Ed448) is out of
    scope — libp11 has no per-key finish callback for it, so there is no equivalent
    hook.
  • Trade-off: intended for many short-lived signing processes. A long-lived
    process that signs repeatedly benefits from the cached login, so it should
    leave the option off — hence opt-in.
  • Background: the skipped exit-time teardown that makes runtime release necessary
    is the behaviour libp11 adopted to avoid an exit-time crash/deadlock (cf. Library cleanup issues with OpenSSL - libp11 - yubihsm_pkcs11.so chain #527).

License Declaration

  • I hereby agree to license my contribution under the project's license.

Signing with one short-lived process per file against a token with a hard
session limit (e.g. YubiHSM 2, 16 sessions) exhausts sessions: libp11 skips its
teardown at process exit (g_shutdown_mode set by the atexit handler), so the
cached login session is never released; and on the YubiHSM only C_Logout (not
C_CloseSession) frees the authenticated session.

This adds an opt-in 'no_login_cache' provider parameter and a public
PKCS11_set_no_login_cache() API. When set, the token is logged out and its
sessions are closed as soon as a private key object is freed (RSA/EC finish
callbacks), during normal runtime instead of at the skipped exit. Re-login
happens on the next key load. Behaviour is unchanged when the parameter is
unset.

Signed-off-by: Dante Melo <dante_melo@hotmail.com>
Signed-off-by: Dante Melo <dante_melo@hotmail.com>
The opt-in no_login_cache option released the login session only from the
RSA/EC method finish callbacks. Those never run on the provider path:
keymgmt_load() transfers ownership of the wrapped P11_KEYDATA to OpenSSL, so
the legacy EVP_PKEY libp11 wraps never reaches refcount 0 during a run -- the
finish callbacks would only fire at process exit, which libp11 deliberately
skips (g_shutdown_mode). As a result the token login session was still leaked
and the 17th short-lived signing process failed with CKR_SESSION_COUNT.
Hook the provider's own key-free instead. keymgmt_free() now calls
p11_keydata_release_login(), which -- for a private key when no_login_cache is
set -- calls the new pkcs11_release_login_for_pkey() to locate the slot via the
wrapped key's RSA/EC ex-data and release the login session during healthy
runtime (C_Logout + close sessions). Covers RSA and ECDSA. The existing
finish-callback hooks are kept for the engine front-end.
Files:
- p11_key.c / libp11-int.h: pkcs11_release_login_for_pkey()
- provider_helpers.c / .h: p11_keydata_release_login() (+ libp11-int.h include)
- provider.c: call it from keymgmt_free()
NOTE: still contains temporary LIBP11-DBG instrumentation; remove before the PR.

Signed-off-by: Dante Melo <dante_melo@hotmail.com>
Signing one file per short-lived process against a token with a hard limit on
concurrent authenticated sessions (e.g. YubiHSM 2, 16 sessions) exhausts them:
libp11 skips its teardown at process exit (g_shutdown_mode), so the cached login
session is never released, and on the YubiHSM only C_Logout (not C_CloseSession)
frees the authenticated session. After 16 files the 17th fails with
CKR_SESSION_COUNT.
Add an opt-in no_login_cache provider parameter (and a public
PKCS11_set_no_login_cache() API). When set, the token is logged out and its
sessions closed as soon as a private key is freed, during normal runtime instead
of at the skipped exit. For the OpenSSL 3 provider this happens in keymgmt_free
(the legacy RSA/EC finish callbacks never run there, since keymgmt_load transfers
ownership of the wrapped key); the engine path uses the finish callbacks. Covers
RSA and ECDSA. Behaviour is unchanged when the parameter is unset.

Signed-off-by: Dante Melo <dante_melo@hotmail.com>
@dantecmelo dantecmelo marked this pull request as ready for review June 11, 2026 21:36
@dantecmelo dantecmelo marked this pull request as draft June 11, 2026 21:36
@dantecmelo dantecmelo marked this pull request as ready for review June 12, 2026 17:07
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.

1 participant