From b5cea76cffc21901e87f0b40c326d3e8820fe29e Mon Sep 17 00:00:00 2001 From: Dante Melo Date: Wed, 10 Jun 2026 16:22:34 -0400 Subject: [PATCH 1/5] Add opt-in no_login_cache to release the token login session on key free 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 --- FORK-AND-PR.md | 417 +++++++++++++++++++++++++++++++++++++++++ NEWS | 4 + README.md | 8 +- src/libp11-int.h | 8 + src/libp11.exports | 1 + src/libp11.h | 16 ++ src/p11_ec.c | 7 + src/p11_front.c | 8 + src/p11_rsa.c | 7 + src/p11_slot.c | 61 ++++++ src/provider_helpers.c | 30 +++ src/util.h | 1 + src/util_uri.c | 8 + 13 files changed, 575 insertions(+), 1 deletion(-) create mode 100644 FORK-AND-PR.md diff --git a/FORK-AND-PR.md b/FORK-AND-PR.md new file mode 100644 index 00000000..6343079f --- /dev/null +++ b/FORK-AND-PR.md @@ -0,0 +1,417 @@ +# Forking `libp11` and opening a pull request + +This guide takes the `no_login_cache` change from your local copy, publishes it +as a **fork** of [`OpenSC/libp11`](https://github.com/OpenSC/libp11), and opens a +**pull request** back to that project — **without needing write access** to it. + +> **Terminology:** a **fork** is a copy of the repo under your account (here +> `dantecmelo/libp11`); a **branch** is a named line of commits inside a repo +> (here `session-fix`). They are not the same thing. + +**Follow steps 1–12 in order.** They assume your goal is a pull request. +If instead you only want your own public fork **without** a PR, do steps 1–2 and +then see **Appendix A**. Throughout, replace `dantecmelo` with your GitHub +username and adjust file paths to your machine. + +> **Two folders are involved:** +> - **Source (your edited copy):** `C:\Users\dante.melo\My Drive\_PS\Projects\MathWorks\libp11-no-deinit-fix` +> - **Clone (this folder, where you prepare the PR):** a fresh clone of your fork. + +--- + +## 1. Install prerequisites + +- A **GitHub account** (free). +- **git** — and optionally the **GitHub CLI** (`gh`), which simplifies auth and + PR creation: + + | OS | Install git | Install gh (optional) | + |----|-------------|------------------------| + | **Debian / Ubuntu / WSL** | `sudo apt install git` | `sudo apt install gh` | + | **macOS** | `xcode-select --install` (or `brew install git`) | `brew install gh` | + | **Windows** | `winget install Git.Git` (or [git-scm.com](https://git-scm.com)) | `winget install GitHub.cli` | + + On **Windows**, run this guide's commands in **Git Bash** so the `bash`-style + commands work; PowerShell variants are given where they differ. + +- *(Optional, only if you want to build to sanity-check — step 7)* the libp11 + build toolchain. Debian/Ubuntu/WSL: `sudo apt install -y build-essential + autoconf automake libtool pkgconf libssl-dev`. macOS: `brew install openssl@3 + autoconf automake libtool pkgconf`. + +- **Set your commit identity** once: + + ```bash + git config --global user.name "Dante Melo" + git config --global user.email "you@example.com" # an email on your GitHub account + ``` + The name is cosmetic; GitHub links commits to you by the **email**, so use a + verified account email or your `…@users.noreply.github.com` address. + +- **Choose how you'll authenticate** when pushing (used in step 9): + - **GitHub CLI (easiest):** `gh auth login` stores credentials and configures git. + - **Personal Access Token (PAT):** pasted as the *password* at the HTTPS prompt + — your account password will **not** work (steps in step 9). + - **SSH key:** `ssh-keygen -t ed25519 -C "you@example.com"`, then add + `~/.ssh/id_ed25519.pub` at GitHub → *Settings → SSH and GPG keys*. + +--- + +## 2. Create your fork + +**Web UI:** open → **Fork** → set **Owner** to +`dantecmelo`, keep the default repository name `libp11` → **Create fork**. You +now have `https://github.com/dantecmelo/libp11`. + +**Or GitHub CLI:** +```bash +gh repo fork OpenSC/libp11 --clone=false +``` + +**(Optional) Set the fork's "About" description:** +```bash +gh repo edit dantecmelo/libp11 \ + --description "Fork of OpenSC/libp11 adding an opt-in no_login_cache provider parameter that releases the token login session on key free to avoid session-slot exhaustion on capped HSMs (e.g. YubiHSM 2)." \ + --add-topic openssl --add-topic pkcs11 --add-topic yubihsm +``` + +--- + +## 3. Clone your fork and add the upstream remote + +A PR needs your branch to share history with OpenSC/libp11, so start from a +**clone of your fork** — **not** from your edited copy. Clone into a *new* folder: + +```bash +git clone https://github.com/dantecmelo/libp11.git +cd libp11 +git remote add upstream https://github.com/OpenSC/libp11.git +git fetch upstream +``` + +Run **all** the remaining git commands from inside this `libp11` folder. If git +prints `fatal: not a git repository`, you're in the wrong directory — `cd` back +into the clone. (Never run git inside your edited copy, and don't `git init` it.) + +> **Already have a working clone** of libp11? Reuse it instead — see **Appendix B**. + +--- + +## 4. Create your branch + +Branch off the latest upstream `main` for the cleanest PR base: + +```bash +git checkout -b session-fix upstream/main +``` + +--- + +## 5. Copy your changed files into the clone + +Copy **only the files this change touches** from your edited copy into the clone. +The PR set is the **10 source files + `NEWS`**, plus the PR README +(`README.pr.md`, which is upstream's README + the `no_login_cache` docs) copied +in **as `README.md`**. + +*Windows PowerShell:* +```powershell +$src = "C:\Users\dante.melo\My Drive\_PS\Projects\MathWorks\libp11-no-deinit-fix" +$files = @( + "src\libp11-int.h", + "src\p11_slot.c", + "src\p11_rsa.c", + "src\p11_ec.c", + "src\p11_front.c", + "src\libp11.h", + "src\libp11.exports", + "src\util.h", + "src\util_uri.c", + "src\provider_helpers.c", + "NEWS" +) +foreach ($f in $files) { Copy-Item -Path (Join-Path $src $f) -Destination $f -Force } +Copy-Item -Path (Join-Path $src "README.pr.md") -Destination "README.md" -Force +``` + +*macOS / Linux / WSL / Git Bash:* +```bash +SRC="/path/to/libp11-no-deinit-fix" +for f in src/libp11-int.h src/p11_slot.c src/p11_rsa.c src/p11_ec.c \ + src/p11_front.c src/libp11.h src/libp11.exports src/util.h \ + src/util_uri.c src/provider_helpers.c NEWS; do + cp "$SRC/$f" "$f" +done +cp "$SRC/README.pr.md" README.md # PR README = upstream README + no_login_cache docs +``` + +> The fork-only files (`README.upstream.md`, `README.pr.md`, `FORK-AND-PR.md`, +> and your fork's own `README.md` with its fork note) are intentionally **not** +> copied into the clone — they don't belong in an upstream PR. To host a custom +> README on your fork separately, see **Appendix C**. + +--- + +## 6. Verify the diff is exactly your change + +```bash +git status +git diff --stat +``` + +Expected: the 10 `src/…` files, `NEWS`, and `README.md`. Spot-check the README: +```bash +git diff README.md # ONLY the no_login_cache bullet + the env-var line +``` +If `git diff` shows a "Fork note", an HTML comment, or `README.upstream.md` / +`README.pr.md`, those slipped in — remove them. If files you never touched show +changes, your copy is based on an older upstream — re-apply just your edits by +hand until the diff is clean. + +--- + +## 7. (Optional) Build to confirm it compiles + +**libp11 has no code-generation step for any file in this change** — unlike some +OpenSSL projects, you do **not** regenerate anything. The only build-system +touchpoint is the symbol exports list `src/libp11.exports`, which already +contains the new `PKCS11_set_no_login_cache` symbol. So you can commit as-is. + +To sanity-check that it builds (needs the toolchain from step 1; this is a fresh +clone, so generate `configure` first): + +```bash +./bootstrap # generate ./configure (git checkout, not a tarball) +./configure +make +# confirm the new public symbol is exported: +nm -D src/.libs/libp11.so | grep PKCS11_set_no_login_cache +``` + +--- + +## 8. Commit your change + +```bash +git add -A +git status # review exactly what's staged +git commit -s -m "Add opt-in no_login_cache to release the token login session on key free + +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." +``` + +The `-s` adds a `Signed-off-by` line (the **DCO** — Developer Certificate of +Origin). Check the repo's `CONTRIBUTING`/`COPYING` for whether it's required; +include `-s` if in doubt. + +--- + +## 9. Push to your fork + +```bash +git push -u origin session-fix +``` + +At the HTTPS prompt, enter username `dantecmelo` and paste a **Personal Access +Token as the password** — **your account password will not work.** + +> **If you see `Invalid username or token. Password authentication is not +> supported…` / `Authentication failed`:** +> +> 1. **Create a token** — fine-grained (recommended): +> → **Generate new token** → +> *Resource owner* `dantecmelo` → *Repository access* → only +> `dantecmelo/libp11` → *Permissions → Contents: Read and write* → +> **Generate**, then copy it (`github_pat_…`). Classic alternative: +> → "Generate new token (classic)" → +> scope `repo`. +> 2. **Push again** and paste the token at the `Password:` prompt (input hidden). +> 3. **Avoid re-prompts:** `git config --global credential.helper store` (caches +> the PAT in `~/.git-credentials`, plaintext), or use the GitHub CLI: +> `gh auth login && gh auth setup-git`. +> +> **SSH alternative:** add an SSH key (step 1), then +> `git remote set-url origin git@github.com:dantecmelo/libp11.git` and push. + +Your branch is now at `https://github.com/dantecmelo/libp11/tree/session-fix`. + +--- + +## 10. Open the pull request + +**Web UI:** visit your fork — GitHub shows a **"Compare & pull request"** banner +for the new branch; click it (otherwise **Contribute → Open pull request**). +Confirm the direction: +- **base:** `OpenSC/libp11` · `main` +- **head:** `dantecmelo/libp11` · `session-fix` + +Leave **"Allow edits by maintainers"** checked, add a title and description +(template below), then **Create pull request** (or the dropdown's **Create draft +pull request** to let CI run first). + +**Or GitHub CLI:** +```bash +gh pr create --repo OpenSC/libp11 --base main \ + --head dantecmelo:session-fix \ + --title "Add opt-in no_login_cache to release the token login session on key free" \ + --body-file PR-BODY.md +# add --draft to open it as a draft +``` + +**Suggested PR description:** +```markdown +## Problem +Signing with one short-lived `openssl` process per file against a token with a +hard limit on concurrent authenticated sessions (notably the YubiHSM 2, max 16) +exhausts the sessions. Each process logs in and the login session is never +released, because libp11 skips its teardown at process exit — the +`g_shutdown_mode` guard set by the `atexit` handler in `util_uri.c` makes +`UTIL_CTX_free_libp11` skip `PKCS11_release_all_slots`/`C_Finalize`. On the +YubiHSM, `C_CloseSession` does not free the authenticated session — only +`C_Logout` does. After 16 files the 17th fails with "could not read private key". + +## Change +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 (in the RSA/EC key +`finish` callbacks), during healthy runtime — not at the skipped exit. Re-login +happens automatically on the next key load. + +## Safety / scope +- Opt-in: with the parameter unset, behaviour is byte-for-byte unchanged. +- The logout helper is self-contained (it does not call `pkcs11_wipe_cache` or + `pkcs11_get_session`) to avoid re-entrancy from the key-free callback; it is + idempotent via a lockless `logged_in` check, so it never recurses or nests + `slot->lock`. +- Covers RSA and EC keys. EdDSA has no per-key finish callback in libp11, so it + is out of scope (documented). + +## Testing +Verified on a YubiHSM 2: a 50-iteration `openssl dgst -sha512 -sign` loop that +previously failed after the 16th now completes; a PKCS#11 trace shows one +`C_Logout` per key free. +``` + +--- + +## 11. After opening the PR + +- **Respond to review** by pushing follow-up commits to the **same branch** + (`git push origin session-fix`) — the PR updates automatically. +- **CI** runs on the PR; watch the checks and fix any failures. +- **Likely requests / things already done for this change:** + - A test under `tests/` exercising the option (maintainers may ask for one). + - **`NEWS` entry** — already added (top "unreleased" section). + - **Doxygen comment** on the new public function — already added in `libp11.h`. + - **Exported symbol** — already added to `src/libp11.exports`. + - A possible design discussion about exposing a new public API + (`PKCS11_set_no_login_cache`) vs keeping the flag internal. It is exposed + publicly because `util_uri.c` already configures libp11 through public + `PKCS11_*` calls (e.g. `PKCS11_set_ui_method`); that's the consistent layering. + - A DCO `Signed-off-by` line if not already present. + +--- + +## 12. Keep your fork up to date + +If upstream moves on, rebase your branch onto the latest `main`: + +```bash +git fetch upstream +git checkout main && git merge --ff-only upstream/main && git push origin main +git checkout session-fix +git rebase main # replay your commits on top; resolve conflicts +git push --force-with-lease origin session-fix +``` + +`--force-with-lease` updates the PR branch safely after a rebase without +clobbering anyone else's pushed changes. + +--- + +## Appendix A — Publish your fork without a PR + +If you just want your code on your own fork (no upstream PR), turn your edited +copy directly into a repo. Its history won't link to upstream — fine for a +personal copy, but no clean PR later (use the main flow for that). + +```bash +cd "/path/to/libp11-no-deinit-fix" # your edited copy +git init +git remote add origin https://github.com/dantecmelo/libp11.git +git add . # the bundled .gitignore keeps build junk out +git commit -m "no_login_cache: release the token login session on key free" +git branch -M session-fix +git push -u origin session-fix # PAT as password — see step 9 +``` + +Push to a **new branch** as shown, **not** `main` (your fork's `main` holds +upstream history; pushing unrelated history there would be rejected). + +--- + +## Appendix B — Reuse an existing clone + +If you already have a clone of libp11, use it instead of cloning fresh in step 3. +Point its remotes at your fork: + +```bash +cd /path/to/your/libp11-clone +git remote rename origin upstream # was OpenSC/libp11 → now "upstream" +git remote add origin https://github.com/dantecmelo/libp11.git +git fetch upstream +git remote -v # upstream=OpenSC, origin=your fork +``` + +Then continue at **step 4**. + +**Linux ownership note:** if the clone was built under `sudo` and is root-owned, +git will refuse to use it. Fix once: `sudo chown -R "$USER":"$USER" ` (or +`git config --global --add safe.directory `). + +--- + +## Appendix C — Host a custom README / handle the fork-only files + +Four files are **fork-only** and must stay **out of the PR**: +`README.upstream.md` (pristine upstream copy), `README.pr.md` (the PR README), +this `FORK-AND-PR.md`, and your edited copy's `README.md` (the one with the fork +note). The PR's `README.md` comes from `README.pr.md` (step 5), so don't copy the +others into the PR branch. + +To show a custom README on your **fork's** page, commit the fork files to a +different branch so they never mix into the PR — e.g. on your fork's `main`: +```bash +git checkout main +# copy README.md (fork version), README.upstream.md, FORK-AND-PR.md in, then: +git add README.md README.upstream.md FORK-AND-PR.md +git commit -m "Fork-specific docs" +git push origin main +git checkout session-fix # back to your PR branch +``` + +--- + +## Quick reference + +| Action | Command | +|---|---| +| Create fork (CLI) | `gh repo fork OpenSC/libp11 --clone=false` | +| Clone fork + upstream | `git clone https://github.com/dantecmelo/libp11.git && cd libp11 && git remote add upstream https://github.com/OpenSC/libp11.git` | +| Create branch | `git checkout -b session-fix upstream/main` | +| Build (sanity check) | `./bootstrap && ./configure && make` | +| Commit (with DCO) | `git commit -s -m "…"` | +| Push to fork | `git push -u origin session-fix` | +| Open PR (CLI) | `gh pr create --repo OpenSC/libp11 --base main --head dantecmelo:session-fix` | +| Sync with upstream | `git fetch upstream && git rebase upstream/main` | diff --git a/NEWS b/NEWS index ecae25fb..312bf8b6 100644 --- a/NEWS +++ b/NEWS @@ -1,6 +1,10 @@ NEWS for Libp11 -- History of user visible changes New in 0.4.19; unreleased +* Added the opt-in "no_login_cache" provider parameter, which releases the + token login session when a key is freed instead of caching it until exit. + Avoids session-slot exhaustion on tokens with a hard session limit (e.g. + YubiHSM 2) driven by repeated short-lived signing processes. (Dante Melo) * Define LIBP11_VERSION_NUMBER (Michał Trojnara) * Added PKCS#11 provider support for OpenSSL 4.x. (Małgorzata Olszówka) diff --git a/README.md b/README.md index dad979ad..3503329f 100644 --- a/README.md +++ b/README.md @@ -131,7 +131,7 @@ activate = 1 Some parameters can be overridden using environment variables: `OPENSSL_MODULES`, `PKCS11_MODULE_PATH`, `PKCS11_DEBUG_LEVEL`, -`PKCS11_FORCE_LOGIN`, `PKCS11_PIN` +`PKCS11_FORCE_LOGIN`, `PKCS11_NO_LOGIN_CACHE`, `PKCS11_PIN` ## Testing the provider operation @@ -208,6 +208,12 @@ The provider supports the following controls: * **debug_level**: Sets the debug level: 0=emerg, 1=alert, 2=crit, 3=err, 4=warning, 5=notice (default), 6=info, 7=debug * **force_login**: Forces login to the PKCS#11 module +* **no_login_cache**: Log out and close the token session as soon as a key is + freed, instead of keeping the login session cached until exit. Opt-in + workaround for tokens with a hard limit on concurrent sessions (e.g. YubiHSM 2, + 16 sessions) driven by many short-lived processes, where exit-time cleanup is + skipped. Re-login happens automatically on the next key load. Leave it off for + long-lived processes that sign repeatedly (they benefit from the cached login). * **init_args**: Specifies additional initialization arguments to the PKCS#11 module diff --git a/src/libp11-int.h b/src/libp11-int.h index f9cee6e1..4be0c79c 100644 --- a/src/libp11-int.h +++ b/src/libp11-int.h @@ -66,6 +66,7 @@ struct pkcs11_ctx_private { unsigned int forkid; int initialized; void (*vlog_a)(int, const char *, va_list); /* for the logging callback */ + int no_login_cache; /* opt-in: drop the login session on key free */ }; struct pkcs11_keys { @@ -79,6 +80,7 @@ struct pkcs11_slot_private { pthread_mutex_t lock; pthread_cond_t cond; int8_t rw_mode, logged_in; + int8_t no_login_cache; /* copied from ctx at slot creation */ CK_SLOT_ID id; CK_SESSION_HANDLE *session_pool; unsigned int session_head, session_tail, session_poolsize; @@ -262,6 +264,9 @@ extern int pkcs11_login(PKCS11_SLOT_private *, int so, const char *pin); /* De-authenticate from the card */ extern int pkcs11_logout(PKCS11_SLOT_private *); +/* Release the login session without wiping the key cache (opt-in no_login_cache) */ +extern int pkcs11_slot_logout_session_only(PKCS11_SLOT_private *); + /* Authenticate a private the key operation if needed */ int pkcs11_authenticate(PKCS11_OBJECT_private *key, CK_SESSION_HANDLE session); @@ -315,6 +320,9 @@ extern int pkcs11_remove_object(PKCS11_OBJECT_private *object); extern int pkcs11_set_ui_method(PKCS11_CTX_private *ctx, UI_METHOD *ui_method, void *ui_user_data); +/* Enable/disable dropping the login session when a key is freed */ +extern int pkcs11_set_no_login_cache(PKCS11_CTX_private *ctx, int enable); + /* Initialize a token */ extern int pkcs11_init_token(PKCS11_SLOT_private *, const char *pin, const char *label); diff --git a/src/libp11.exports b/src/libp11.exports index 75ef61ca..1d2e86ee 100644 --- a/src/libp11.exports +++ b/src/libp11.exports @@ -53,6 +53,7 @@ PKCS11_get_ecdh_method PKCS11_pkey_meths ERR_load_PKCS11_strings PKCS11_set_ui_method +PKCS11_set_no_login_cache ERR_get_CKR_code PKCS11_set_vlog_a_method PKCS11_keygen diff --git a/src/libp11.h b/src/libp11.h index e9c9df01..1eeff299 100644 --- a/src/libp11.h +++ b/src/libp11.h @@ -384,6 +384,22 @@ extern int PKCS11_remove_certificate(PKCS11_CERT *); extern int PKCS11_set_ui_method(PKCS11_CTX *ctx, UI_METHOD *ui_method, void *ui_user_data); +/** + * Enable or disable dropping the token login session when a key is freed. + * + * When enabled, the login session is logged out and closed as soon as a private + * key object is freed, instead of being cached until the context is destroyed. + * This is an opt-in workaround for tokens with a hard limit on concurrent + * sessions (e.g. YubiHSM 2) driven by many short-lived processes, where + * exit-time cleanup is skipped. Re-login happens automatically on the next key + * load. Leave it disabled for long-lived processes that sign repeatedly. + * + * @param ctx context allocated by PKCS11_CTX_new() + * @param enable non-zero to drop the login session on key free, 0 to cache it + * @return 0 on success, -1 on error + */ +extern int PKCS11_set_no_login_cache(PKCS11_CTX *ctx, int enable); + /** * Initialize a token * diff --git a/src/p11_ec.c b/src/p11_ec.c index 50d18ba2..e38f10a3 100644 --- a/src/p11_ec.c +++ b/src/p11_ec.c @@ -423,7 +423,14 @@ static void pkcs11_ec_finish(EC_KEY *ec) key = pkcs11_get_ex_data_ec(ec); if (key) { + PKCS11_SLOT_private *slot = key->slot; + pkcs11_set_ex_data_ec(ec, NULL); + /* opt-in: release the login session now, during healthy runtime, + * instead of leaking it because cleanup is skipped at exit */ + if (slot && slot->no_login_cache + && key->object_class == CKO_PRIVATE_KEY) + pkcs11_slot_logout_session_only(slot); pkcs11_object_free(key); } if (ossl_ec_finish) diff --git a/src/p11_front.c b/src/p11_front.c index e8f19f5d..e411e91a 100644 --- a/src/p11_front.c +++ b/src/p11_front.c @@ -409,6 +409,14 @@ int PKCS11_set_ui_method(PKCS11_CTX *pctx, UI_METHOD *ui_method, void *ui_user_d return pkcs11_set_ui_method(ctx, ui_method, ui_user_data); } +int PKCS11_set_no_login_cache(PKCS11_CTX *pctx, int enable) +{ + PKCS11_CTX_private *ctx = pctx->_private; + if (check_fork(ctx) < 0) + return -1; + return pkcs11_set_no_login_cache(ctx, enable); +} + /* External interface to the deprecated features */ int PKCS11_keygen(PKCS11_TOKEN *token, PKCS11_KGEN_ATTRS *kg) diff --git a/src/p11_rsa.c b/src/p11_rsa.c index 8d764864..2c5f2749 100644 --- a/src/p11_rsa.c +++ b/src/p11_rsa.c @@ -430,7 +430,14 @@ static int pkcs11_rsa_free_method(RSA *rsa) RSA_meth_get_finish(RSA_get_default_method()); if (key) { + PKCS11_SLOT_private *slot = key->slot; + pkcs11_set_ex_data_rsa(rsa, NULL); + /* opt-in: release the login session now, during healthy runtime, + * instead of leaking it because cleanup is skipped at exit */ + if (slot && slot->no_login_cache + && key->object_class == CKO_PRIVATE_KEY) + pkcs11_slot_logout_session_only(slot); pkcs11_object_free(key); } if (orig_rsa_free_method) { diff --git a/src/p11_slot.c b/src/p11_slot.c index 56a5c4ad..5333796a 100644 --- a/src/p11_slot.c +++ b/src/p11_slot.c @@ -294,6 +294,66 @@ int pkcs11_logout(PKCS11_SLOT_private *slot) return 0; } +/* + * Release the authenticated login session WITHOUT touching the key cache. + * + * Used by the opt-in no_login_cache behavior: it is called from the RSA/EC + * key "finish" callbacks (i.e. from inside EVP_PKEY_free), so it must NOT call + * pkcs11_logout()/pkcs11_wipe_cache() (those free cached keys via EVP_PKEY_free + * and would re-enter the finish callback -> double free) and must NOT call + * pkcs11_get_session() (which can itself wipe the cache while holding the slot + * lock). It borrows a pooled session handle directly, issues C_Logout (which on + * tokens such as the YubiHSM is what actually releases the authenticated + * session -- C_CloseSession alone does not), then closes and resets the pool. + * + * Idempotent: a lockless logged_in check makes repeated/re-entrant calls a + * no-op, so it never recurses and never nests slot->lock. + */ +int pkcs11_slot_logout_session_only(PKCS11_SLOT_private *slot) +{ + PKCS11_CTX_private *ctx = slot->ctx; + CK_SESSION_HANDLE session = CK_INVALID_HANDLE; + + if (slot->logged_in < 0) /* not logged in: nothing to do, no lock taken */ + return 0; + + pthread_mutex_lock(&slot->lock); + if (slot->logged_in < 0) { /* lost a race with another logout */ + pthread_mutex_unlock(&slot->lock); + return 0; + } + slot->logged_in = -1; + if (slot->session_head != slot->session_tail) { + /* borrow a pooled session handle to log out on */ + session = slot->session_pool[slot->session_head]; + slot->session_head = (slot->session_head + 1) % slot->session_poolsize; + } + pthread_mutex_unlock(&slot->lock); + + /* C_Logout releases the authenticated session on the token. Best effort. */ + if (session != CK_INVALID_HANDLE) + CRYPTOKI_call(ctx, C_Logout(session)); + + pthread_mutex_lock(&slot->lock); + CRYPTOKI_call(ctx, C_CloseAllSessions(slot->id)); + slot->num_sessions = 0; + slot->session_head = slot->session_tail = 0; + pthread_mutex_unlock(&slot->lock); + + return 0; +} + +/* + * Enable/disable dropping the login session when a key is freed + */ +int pkcs11_set_no_login_cache(PKCS11_CTX_private *ctx, int enable) +{ + if (!ctx) + return -1; + ctx->no_login_cache = enable ? 1 : 0; + return 0; +} + /* * Initialize the token */ @@ -444,6 +504,7 @@ static PKCS11_SLOT_private *pkcs11_slot_new(PKCS11_CTX_private *ctx, CK_SLOT_ID slot->id = id; slot->forkid = ctx->forkid; slot->logged_in = -1; + slot->no_login_cache = (int8_t)ctx->no_login_cache; slot->rw_mode = -1; slot->max_sessions = 16; slot->session_poolsize = slot->max_sessions + 1; diff --git a/src/provider_helpers.c b/src/provider_helpers.c index e7e764e9..523adec2 100644 --- a/src/provider_helpers.c +++ b/src/provider_helpers.c @@ -45,6 +45,7 @@ typedef struct { char *pin; char *debug_level; char *force_login; + char *no_login_cache; char *init_args; } PROVIDER_PARAMS; @@ -65,8 +66,10 @@ struct provider_ctx { char *pin; int debug_level; int force_login; + int no_login_cache; char *p_debug_level; char *p_force_login; + char *p_no_login_cache; /* function offered by libcrypto to the provider */ OSSL_FUNC_core_get_params_fn *core_get_params; @@ -240,6 +243,7 @@ void PROVIDER_CTX_destroy(PROVIDER_CTX *prov_ctx) OPENSSL_free(prov_ctx->pin); OPENSSL_free(prov_ctx->p_debug_level); OPENSSL_free(prov_ctx->p_force_login); + OPENSSL_free(prov_ctx->p_no_login_cache); OPENSSL_free(prov_ctx->init_args); OPENSSL_free(prov_ctx); } @@ -283,6 +287,7 @@ int PROVIDER_CTX_get_core_parameters(PROVIDER_CTX *prov_ctx) {"pin", OSSL_PARAM_UTF8_PTR, &prov_ctx->params.pin, 0, 0}, {"debug_level", OSSL_PARAM_UTF8_PTR, &prov_ctx->params.debug_level, 0, 0}, {"force_login", OSSL_PARAM_UTF8_PTR, &prov_ctx->params.force_login, 0, 0}, + {"no_login_cache", OSSL_PARAM_UTF8_PTR, &prov_ctx->params.no_login_cache, 0, 0}, {"init_args", OSSL_PARAM_UTF8_PTR, &prov_ctx->params.init_args, 0, 0}, OSSL_PARAM_END }; @@ -324,6 +329,9 @@ int PROVIDER_CTX_get_core_parameters(PROVIDER_CTX *prov_ctx) if (prov_ctx->params.force_login) { prov_ctx->p_force_login = OPENSSL_strdup(prov_ctx->params.force_login); } + if (prov_ctx->params.no_login_cache) { + prov_ctx->p_no_login_cache = OPENSSL_strdup(prov_ctx->params.no_login_cache); + } if (prov_ctx->params.init_args) { prov_ctx->init_args = OPENSSL_strdup(prov_ctx->params.init_args); } @@ -383,6 +391,17 @@ int PROVIDER_CTX_set_parameters(PROVIDER_CTX *prov_ctx) if (prov_ctx->force_login) { UTIL_CTX_set_force_login(prov_ctx->util_ctx, 1); } + if (prov_ctx->p_no_login_cache && *prov_ctx->p_no_login_cache != '\0') { + if (isdigit(*prov_ctx->p_no_login_cache)) { + prov_ctx->no_login_cache = (atoi(prov_ctx->p_no_login_cache) != 0); + } else { + prov_ctx->no_login_cache = (strcasecmp("true", prov_ctx->p_no_login_cache) == 0 + || strcasecmp("yes", prov_ctx->p_no_login_cache) == 0); + } + } + if (prov_ctx->no_login_cache) { + UTIL_CTX_set_no_login_cache(prov_ctx->util_ctx, 1); + } return 1; } @@ -1662,6 +1681,11 @@ static void PROVIDER_CTX_get_environment_parameters(PROVIDER_CTX *prov_ctx) OPENSSL_free(prov_ctx->p_force_login); prov_ctx->p_force_login = OPENSSL_strdup(str); } + str = getenv("PKCS11_NO_LOGIN_CACHE"); + if (str != NULL && str[0] != '\0') { + OPENSSL_free(prov_ctx->p_no_login_cache); + prov_ctx->p_no_login_cache = OPENSSL_strdup(str); + } } /* @@ -1678,6 +1702,7 @@ static int PROVIDER_CTX_get_specific_parameters(PROVIDER_CTX *prov_ctx) {"pin", OSSL_PARAM_UTF8_PTR, ¶ms.pin, 0, 0}, {"debug_level", OSSL_PARAM_UTF8_PTR, ¶ms.debug_level, 0, 0}, {"force_login", OSSL_PARAM_UTF8_PTR, ¶ms.force_login, 0, 0}, + {"no_login_cache", OSSL_PARAM_UTF8_PTR, ¶ms.no_login_cache, 0, 0}, {"init_args", OSSL_PARAM_UTF8_PTR, ¶ms.init_args, 0, 0}, OSSL_PARAM_END }; @@ -1712,6 +1737,11 @@ static int PROVIDER_CTX_get_specific_parameters(PROVIDER_CTX *prov_ctx) OPENSSL_free(prov_ctx->p_force_login); prov_ctx->p_force_login = OPENSSL_strdup(params.force_login); } + if (params.no_login_cache && (!prov_ctx->params.no_login_cache + || strcmp(params.no_login_cache, prov_ctx->params.no_login_cache))) { + OPENSSL_free(prov_ctx->p_no_login_cache); + prov_ctx->p_no_login_cache = OPENSSL_strdup(params.no_login_cache); + } if (params.init_args && (!prov_ctx->params.init_args || strcmp(params.init_args, prov_ctx->params.init_args))) { OPENSSL_free(prov_ctx->init_args); diff --git a/src/util.h b/src/util.h index a1379e72..334635d6 100644 --- a/src/util.h +++ b/src/util.h @@ -71,6 +71,7 @@ void UTIL_CTX_log(UTIL_CTX *ctx, int level, const char *format, ...) int UTIL_CTX_set_pin(UTIL_CTX *ctx, const char *pin); void UTIL_CTX_set_force_login(UTIL_CTX *ctx, int force_login); +void UTIL_CTX_set_no_login_cache(UTIL_CTX *ctx, int no_login_cache); X509 *UTIL_CTX_get_cert_from_uri(UTIL_CTX *ctx, const char *uri, UI_METHOD *ui_method, void *ui_data); diff --git a/src/util_uri.c b/src/util_uri.c index 2bfc5198..3e558813 100644 --- a/src/util_uri.c +++ b/src/util_uri.c @@ -67,6 +67,7 @@ struct util_ctx_st { size_t pin_length; int forced_pin; int force_login; + int no_login_cache; /* Current operations */ PKCS11_CTX *pkcs11_ctx; @@ -173,6 +174,8 @@ static int util_ctx_init_libp11(UTIL_CTX *ctx) PKCS11_set_vlog_a_method(ctx->pkcs11_ctx, ctx->vlog); PKCS11_CTX_init_args(ctx->pkcs11_ctx, ctx->init_args); PKCS11_set_ui_method(ctx->pkcs11_ctx, ctx->ui_method, ctx->ui_data); + if (ctx->no_login_cache) + PKCS11_set_no_login_cache(ctx->pkcs11_ctx, 1); if (PKCS11_CTX_load(ctx->pkcs11_ctx, ctx->module) < 0) { UTIL_CTX_log(ctx, LOG_ERR, "Unable to load module %s\n", ctx->module); UTIL_CTX_free_libp11(ctx); @@ -470,6 +473,11 @@ void UTIL_CTX_set_force_login(UTIL_CTX *ctx, int force_login) ctx->force_login = force_login; } +void UTIL_CTX_set_no_login_cache(UTIL_CTX *ctx, int no_login_cache) +{ + ctx->no_login_cache = no_login_cache; +} + /* Return 1 if the user has already logged in */ static int slot_logged_in(UTIL_CTX *ctx, PKCS11_SLOT *slot) { int logged_in = 0; From 047671107925fbdcdcd4afa404957d83afb647cf Mon Sep 17 00:00:00 2001 From: Dante Melo Date: Wed, 10 Jun 2026 17:02:27 -0400 Subject: [PATCH 2/5] WIP: temporary LIBP11-DBG instrumentation for session-leak diagnosis Signed-off-by: Dante Melo --- src/p11_rsa.c | 3 +++ src/p11_slot.c | 7 ++++++- src/util_uri.c | 1 + 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/p11_rsa.c b/src/p11_rsa.c index 2c5f2749..b40291db 100644 --- a/src/p11_rsa.c +++ b/src/p11_rsa.c @@ -433,6 +433,9 @@ static int pkcs11_rsa_free_method(RSA *rsa) PKCS11_SLOT_private *slot = key->slot; pkcs11_set_ex_data_rsa(rsa, NULL); + fprintf(stderr, "LIBP11-DBG: rsa_free no_login_cache=%d class=%ld logged_in=%d\n", + slot ? slot->no_login_cache : -1, (long)key->object_class, + slot ? slot->logged_in : -99); fflush(stderr); /* TEMP DEBUG */ /* opt-in: release the login session now, during healthy runtime, * instead of leaking it because cleanup is skipped at exit */ if (slot && slot->no_login_cache diff --git a/src/p11_slot.c b/src/p11_slot.c index 5333796a..612936ba 100644 --- a/src/p11_slot.c +++ b/src/p11_slot.c @@ -314,6 +314,7 @@ int pkcs11_slot_logout_session_only(PKCS11_SLOT_private *slot) PKCS11_CTX_private *ctx = slot->ctx; CK_SESSION_HANDLE session = CK_INVALID_HANDLE; + fprintf(stderr, "LIBP11-DBG: logout_session_only entry logged_in=%d\n", slot->logged_in); fflush(stderr); /* TEMP DEBUG */ if (slot->logged_in < 0) /* not logged in: nothing to do, no lock taken */ return 0; @@ -331,8 +332,12 @@ int pkcs11_slot_logout_session_only(PKCS11_SLOT_private *slot) pthread_mutex_unlock(&slot->lock); /* C_Logout releases the authenticated session on the token. Best effort. */ - if (session != CK_INVALID_HANDLE) + if (session != CK_INVALID_HANDLE) { + fprintf(stderr, "LIBP11-DBG: calling C_Logout on session %lu\n", (unsigned long)session); fflush(stderr); /* TEMP DEBUG */ CRYPTOKI_call(ctx, C_Logout(session)); + } else { + fprintf(stderr, "LIBP11-DBG: no pooled session, C_Logout SKIPPED\n"); fflush(stderr); /* TEMP DEBUG */ + } pthread_mutex_lock(&slot->lock); CRYPTOKI_call(ctx, C_CloseAllSessions(slot->id)); diff --git a/src/util_uri.c b/src/util_uri.c index 3e558813..6170606c 100644 --- a/src/util_uri.c +++ b/src/util_uri.c @@ -176,6 +176,7 @@ static int util_ctx_init_libp11(UTIL_CTX *ctx) PKCS11_set_ui_method(ctx->pkcs11_ctx, ctx->ui_method, ctx->ui_data); if (ctx->no_login_cache) PKCS11_set_no_login_cache(ctx->pkcs11_ctx, 1); + fprintf(stderr, "LIBP11-DBG: util_ctx_init no_login_cache=%d\n", ctx->no_login_cache); fflush(stderr); /* TEMP DEBUG */ if (PKCS11_CTX_load(ctx->pkcs11_ctx, ctx->module) < 0) { UTIL_CTX_log(ctx, LOG_ERR, "Unable to load module %s\n", ctx->module); UTIL_CTX_free_libp11(ctx); From f1b45e644c9a8398607953e4c243b88b60d8135e Mon Sep 17 00:00:00 2001 From: Dante Melo Date: Wed, 10 Jun 2026 17:28:18 -0400 Subject: [PATCH 3/5] Fix no_login_cache on the OpenSSL 3 provider path (hook keymgmt_free) 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 --- src/libp11-int.h | 3 +++ src/p11_key.c | 37 +++++++++++++++++++++++++++++++++++++ src/provider.c | 4 ++++ src/provider_helpers.c | 20 ++++++++++++++++++++ src/provider_helpers.h | 1 + 5 files changed, 65 insertions(+) diff --git a/src/libp11-int.h b/src/libp11-int.h index 4be0c79c..5f5f50a3 100644 --- a/src/libp11-int.h +++ b/src/libp11-int.h @@ -267,6 +267,9 @@ extern int pkcs11_logout(PKCS11_SLOT_private *); /* Release the login session without wiping the key cache (opt-in no_login_cache) */ extern int pkcs11_slot_logout_session_only(PKCS11_SLOT_private *); +/* Release the login session for the slot backing an EVP_PKEY (provider key-free path) */ +extern void pkcs11_release_login_for_pkey(EVP_PKEY *pkey); + /* Authenticate a private the key operation if needed */ int pkcs11_authenticate(PKCS11_OBJECT_private *key, CK_SESSION_HANDLE session); diff --git a/src/p11_key.c b/src/p11_key.c index 29d5f216..94239b0e 100644 --- a/src/p11_key.c +++ b/src/p11_key.c @@ -327,6 +327,43 @@ void pkcs11_object_free(PKCS11_OBJECT_private *obj) OPENSSL_free(obj); } +/* + * Release the cached login session for the slot backing this EVP_PKEY, if the + * no_login_cache option is enabled for that slot. Used by the OpenSSL 3 provider + * key-free path (keymgmt_free), where the legacy RSA/EC "finish" callbacks do not + * run because keymgmt_load transfers ownership of the wrapped key to OpenSSL. + */ +void pkcs11_release_login_for_pkey(EVP_PKEY *pkey) +{ + PKCS11_OBJECT_private *obj = NULL; + + if (pkey == NULL) + return; + switch (EVP_PKEY_base_id(pkey)) { + case EVP_PKEY_RSA: { + const RSA *rsa = EVP_PKEY_get0_RSA(pkey); + if (rsa) + obj = pkcs11_get_ex_data_rsa(rsa); + break; + } +#ifndef OPENSSL_NO_EC + case EVP_PKEY_EC: { + const EC_KEY *ec = EVP_PKEY_get0_EC_KEY(pkey); + if (ec) + obj = pkcs11_get_ex_data_ec(ec); + break; + } +#endif + default: + break; + } + fprintf(stderr, "LIBP11-DBG: release_login_for_pkey obj=%p slot_no_login_cache=%d\n", + (void *)obj, (obj && obj->slot) ? obj->slot->no_login_cache : -1); + fflush(stderr); /* TEMP DEBUG */ + if (obj != NULL && obj->slot != NULL && obj->slot->no_login_cache) + pkcs11_slot_logout_session_only(obj->slot); +} + /* Set UI method to allow retrieving CKU_CONTEXT_SPECIFIC PINs interactively */ int pkcs11_set_ui_method(PKCS11_CTX_private *ctx, UI_METHOD *ui_method, void *ui_user_data) diff --git a/src/provider.c b/src/provider.c index 4aa136dc..4b0dfa6c 100644 --- a/src/provider.c +++ b/src/provider.c @@ -423,6 +423,10 @@ static void *keymgmt_load(const void *reference, size_t reference_sz) /* Free key management object and release associated resources. */ static void keymgmt_free(void *provkey) { + fprintf(stderr, "LIBP11-DBG: keymgmt_free called provkey=%p\n", provkey); fflush(stderr); /* TEMP DEBUG */ + /* opt-in: release the login session during healthy runtime, since the legacy + * RSA/EC finish callbacks never run on the provider path */ + p11_keydata_release_login(provkey); p11_keydata_free(provkey); } diff --git a/src/provider_helpers.c b/src/provider_helpers.c index 523adec2..2a9172e5 100644 --- a/src/provider_helpers.c +++ b/src/provider_helpers.c @@ -24,6 +24,7 @@ */ #include "provider_helpers.h" +#include "libp11-int.h" /* pkcs11_release_login_for_pkey() */ #include /* isdigit() */ #if defined(__GNUC__) || defined(__clang__) @@ -531,6 +532,25 @@ void p11_keydata_free(P11_KEYDATA *keydata) OPENSSL_free(keydata); } +/* + * Opt-in (no_login_cache): release the token login session for a private key as + * soon as the provider frees it (keymgmt_free), during healthy runtime, instead + * of leaking it because exit-time cleanup is skipped. Best effort; no-op when the + * option is off or the key is public. + */ +void p11_keydata_release_login(P11_KEYDATA *keydata) +{ + fprintf(stderr, "LIBP11-DBG: keydata_release_login is_private=%d no_login_cache=%d\n", + keydata ? keydata->is_private : -1, + (keydata && keydata->prov_ctx) ? keydata->prov_ctx->no_login_cache : -1); + fflush(stderr); /* TEMP DEBUG */ + if (keydata == NULL || !keydata->is_private || keydata->pkey == NULL) + return; + if (keydata->prov_ctx == NULL || !keydata->prov_ctx->no_login_cache) + return; + pkcs11_release_login_for_pkey(keydata->pkey); +} + /* Create keydata object from EVP_PKEY and initialize key metadata. */ P11_KEYDATA *p11_keydata_from_evp_pkey(PROVIDER_CTX *ctx, EVP_PKEY *pkey, int is_private) { diff --git a/src/provider_helpers.h b/src/provider_helpers.h index 1857cee6..25fffe76 100644 --- a/src/provider_helpers.h +++ b/src/provider_helpers.h @@ -79,6 +79,7 @@ int PROVIDER_CTX_set_ui_method(PROVIDER_CTX *prov_ctx, UI_METHOD *ui_method, voi P11_KEYDATA *p11_keydata_new(PROVIDER_CTX *ctx); int p11_keydata_up_ref(P11_KEYDATA *keydata); void p11_keydata_free(P11_KEYDATA *keydata); +void p11_keydata_release_login(P11_KEYDATA *keydata); P11_KEYDATA *p11_keydata_from_evp_pkey(PROVIDER_CTX *ctx, EVP_PKEY *pkey, int is_private); const char *p11_keydata_get_name(P11_KEYDATA *keydata); int p11_keydata_is_private(const P11_KEYDATA *keydata); From ee58b4e3024816c343cfd7fb2a662942750c4479 Mon Sep 17 00:00:00 2001 From: Dante Melo Date: Wed, 10 Jun 2026 17:49:00 -0400 Subject: [PATCH 4/5] Add opt-in no_login_cache to release the token login session on key free 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 --- FORK-AND-PR.md | 7 +++++-- src/p11_key.c | 3 --- src/p11_rsa.c | 3 --- src/p11_slot.c | 7 +------ src/provider.c | 1 - src/provider_helpers.c | 4 ---- src/util_uri.c | 1 - 7 files changed, 6 insertions(+), 20 deletions(-) diff --git a/FORK-AND-PR.md b/FORK-AND-PR.md index 6343079f..cc66e8a3 100644 --- a/FORK-AND-PR.md +++ b/FORK-AND-PR.md @@ -119,6 +119,7 @@ in **as `README.md`**. $src = "C:\Users\dante.melo\My Drive\_PS\Projects\MathWorks\libp11-no-deinit-fix" $files = @( "src\libp11-int.h", + "src\p11_key.c", "src\p11_slot.c", "src\p11_rsa.c", "src\p11_ec.c", @@ -127,7 +128,9 @@ $files = @( "src\libp11.exports", "src\util.h", "src\util_uri.c", + "src\provider.c", "src\provider_helpers.c", + "src\provider_helpers.h", "NEWS" ) foreach ($f in $files) { Copy-Item -Path (Join-Path $src $f) -Destination $f -Force } @@ -137,9 +140,9 @@ Copy-Item -Path (Join-Path $src "README.pr.md") -Destination "README.md" -Force *macOS / Linux / WSL / Git Bash:* ```bash SRC="/path/to/libp11-no-deinit-fix" -for f in src/libp11-int.h src/p11_slot.c src/p11_rsa.c src/p11_ec.c \ +for f in src/libp11-int.h src/p11_key.c src/p11_slot.c src/p11_rsa.c src/p11_ec.c \ src/p11_front.c src/libp11.h src/libp11.exports src/util.h \ - src/util_uri.c src/provider_helpers.c NEWS; do + src/util_uri.c src/provider.c src/provider_helpers.c src/provider_helpers.h NEWS; do cp "$SRC/$f" "$f" done cp "$SRC/README.pr.md" README.md # PR README = upstream README + no_login_cache docs diff --git a/src/p11_key.c b/src/p11_key.c index 94239b0e..f8d18844 100644 --- a/src/p11_key.c +++ b/src/p11_key.c @@ -357,9 +357,6 @@ void pkcs11_release_login_for_pkey(EVP_PKEY *pkey) default: break; } - fprintf(stderr, "LIBP11-DBG: release_login_for_pkey obj=%p slot_no_login_cache=%d\n", - (void *)obj, (obj && obj->slot) ? obj->slot->no_login_cache : -1); - fflush(stderr); /* TEMP DEBUG */ if (obj != NULL && obj->slot != NULL && obj->slot->no_login_cache) pkcs11_slot_logout_session_only(obj->slot); } diff --git a/src/p11_rsa.c b/src/p11_rsa.c index b40291db..2c5f2749 100644 --- a/src/p11_rsa.c +++ b/src/p11_rsa.c @@ -433,9 +433,6 @@ static int pkcs11_rsa_free_method(RSA *rsa) PKCS11_SLOT_private *slot = key->slot; pkcs11_set_ex_data_rsa(rsa, NULL); - fprintf(stderr, "LIBP11-DBG: rsa_free no_login_cache=%d class=%ld logged_in=%d\n", - slot ? slot->no_login_cache : -1, (long)key->object_class, - slot ? slot->logged_in : -99); fflush(stderr); /* TEMP DEBUG */ /* opt-in: release the login session now, during healthy runtime, * instead of leaking it because cleanup is skipped at exit */ if (slot && slot->no_login_cache diff --git a/src/p11_slot.c b/src/p11_slot.c index 612936ba..5333796a 100644 --- a/src/p11_slot.c +++ b/src/p11_slot.c @@ -314,7 +314,6 @@ int pkcs11_slot_logout_session_only(PKCS11_SLOT_private *slot) PKCS11_CTX_private *ctx = slot->ctx; CK_SESSION_HANDLE session = CK_INVALID_HANDLE; - fprintf(stderr, "LIBP11-DBG: logout_session_only entry logged_in=%d\n", slot->logged_in); fflush(stderr); /* TEMP DEBUG */ if (slot->logged_in < 0) /* not logged in: nothing to do, no lock taken */ return 0; @@ -332,12 +331,8 @@ int pkcs11_slot_logout_session_only(PKCS11_SLOT_private *slot) pthread_mutex_unlock(&slot->lock); /* C_Logout releases the authenticated session on the token. Best effort. */ - if (session != CK_INVALID_HANDLE) { - fprintf(stderr, "LIBP11-DBG: calling C_Logout on session %lu\n", (unsigned long)session); fflush(stderr); /* TEMP DEBUG */ + if (session != CK_INVALID_HANDLE) CRYPTOKI_call(ctx, C_Logout(session)); - } else { - fprintf(stderr, "LIBP11-DBG: no pooled session, C_Logout SKIPPED\n"); fflush(stderr); /* TEMP DEBUG */ - } pthread_mutex_lock(&slot->lock); CRYPTOKI_call(ctx, C_CloseAllSessions(slot->id)); diff --git a/src/provider.c b/src/provider.c index 4b0dfa6c..75b4c45b 100644 --- a/src/provider.c +++ b/src/provider.c @@ -423,7 +423,6 @@ static void *keymgmt_load(const void *reference, size_t reference_sz) /* Free key management object and release associated resources. */ static void keymgmt_free(void *provkey) { - fprintf(stderr, "LIBP11-DBG: keymgmt_free called provkey=%p\n", provkey); fflush(stderr); /* TEMP DEBUG */ /* opt-in: release the login session during healthy runtime, since the legacy * RSA/EC finish callbacks never run on the provider path */ p11_keydata_release_login(provkey); diff --git a/src/provider_helpers.c b/src/provider_helpers.c index 2a9172e5..5c0affb6 100644 --- a/src/provider_helpers.c +++ b/src/provider_helpers.c @@ -540,10 +540,6 @@ void p11_keydata_free(P11_KEYDATA *keydata) */ void p11_keydata_release_login(P11_KEYDATA *keydata) { - fprintf(stderr, "LIBP11-DBG: keydata_release_login is_private=%d no_login_cache=%d\n", - keydata ? keydata->is_private : -1, - (keydata && keydata->prov_ctx) ? keydata->prov_ctx->no_login_cache : -1); - fflush(stderr); /* TEMP DEBUG */ if (keydata == NULL || !keydata->is_private || keydata->pkey == NULL) return; if (keydata->prov_ctx == NULL || !keydata->prov_ctx->no_login_cache) diff --git a/src/util_uri.c b/src/util_uri.c index 6170606c..3e558813 100644 --- a/src/util_uri.c +++ b/src/util_uri.c @@ -176,7 +176,6 @@ static int util_ctx_init_libp11(UTIL_CTX *ctx) PKCS11_set_ui_method(ctx->pkcs11_ctx, ctx->ui_method, ctx->ui_data); if (ctx->no_login_cache) PKCS11_set_no_login_cache(ctx->pkcs11_ctx, 1); - fprintf(stderr, "LIBP11-DBG: util_ctx_init no_login_cache=%d\n", ctx->no_login_cache); fflush(stderr); /* TEMP DEBUG */ if (PKCS11_CTX_load(ctx->pkcs11_ctx, ctx->module) < 0) { UTIL_CTX_log(ctx, LOG_ERR, "Unable to load module %s\n", ctx->module); UTIL_CTX_free_libp11(ctx); From a2753c9af8943855dd5c3cf485553c07606e00a7 Mon Sep 17 00:00:00 2001 From: Dante Melo <149836148+dantecmelo@users.noreply.github.com> Date: Fri, 12 Jun 2026 18:15:01 -0400 Subject: [PATCH 5/5] Delete FORK-AND-PR.md This file was not supposed to be part of this PR. --- FORK-AND-PR.md | 420 ------------------------------------------------- 1 file changed, 420 deletions(-) delete mode 100644 FORK-AND-PR.md diff --git a/FORK-AND-PR.md b/FORK-AND-PR.md deleted file mode 100644 index cc66e8a3..00000000 --- a/FORK-AND-PR.md +++ /dev/null @@ -1,420 +0,0 @@ -# Forking `libp11` and opening a pull request - -This guide takes the `no_login_cache` change from your local copy, publishes it -as a **fork** of [`OpenSC/libp11`](https://github.com/OpenSC/libp11), and opens a -**pull request** back to that project — **without needing write access** to it. - -> **Terminology:** a **fork** is a copy of the repo under your account (here -> `dantecmelo/libp11`); a **branch** is a named line of commits inside a repo -> (here `session-fix`). They are not the same thing. - -**Follow steps 1–12 in order.** They assume your goal is a pull request. -If instead you only want your own public fork **without** a PR, do steps 1–2 and -then see **Appendix A**. Throughout, replace `dantecmelo` with your GitHub -username and adjust file paths to your machine. - -> **Two folders are involved:** -> - **Source (your edited copy):** `C:\Users\dante.melo\My Drive\_PS\Projects\MathWorks\libp11-no-deinit-fix` -> - **Clone (this folder, where you prepare the PR):** a fresh clone of your fork. - ---- - -## 1. Install prerequisites - -- A **GitHub account** (free). -- **git** — and optionally the **GitHub CLI** (`gh`), which simplifies auth and - PR creation: - - | OS | Install git | Install gh (optional) | - |----|-------------|------------------------| - | **Debian / Ubuntu / WSL** | `sudo apt install git` | `sudo apt install gh` | - | **macOS** | `xcode-select --install` (or `brew install git`) | `brew install gh` | - | **Windows** | `winget install Git.Git` (or [git-scm.com](https://git-scm.com)) | `winget install GitHub.cli` | - - On **Windows**, run this guide's commands in **Git Bash** so the `bash`-style - commands work; PowerShell variants are given where they differ. - -- *(Optional, only if you want to build to sanity-check — step 7)* the libp11 - build toolchain. Debian/Ubuntu/WSL: `sudo apt install -y build-essential - autoconf automake libtool pkgconf libssl-dev`. macOS: `brew install openssl@3 - autoconf automake libtool pkgconf`. - -- **Set your commit identity** once: - - ```bash - git config --global user.name "Dante Melo" - git config --global user.email "you@example.com" # an email on your GitHub account - ``` - The name is cosmetic; GitHub links commits to you by the **email**, so use a - verified account email or your `…@users.noreply.github.com` address. - -- **Choose how you'll authenticate** when pushing (used in step 9): - - **GitHub CLI (easiest):** `gh auth login` stores credentials and configures git. - - **Personal Access Token (PAT):** pasted as the *password* at the HTTPS prompt - — your account password will **not** work (steps in step 9). - - **SSH key:** `ssh-keygen -t ed25519 -C "you@example.com"`, then add - `~/.ssh/id_ed25519.pub` at GitHub → *Settings → SSH and GPG keys*. - ---- - -## 2. Create your fork - -**Web UI:** open → **Fork** → set **Owner** to -`dantecmelo`, keep the default repository name `libp11` → **Create fork**. You -now have `https://github.com/dantecmelo/libp11`. - -**Or GitHub CLI:** -```bash -gh repo fork OpenSC/libp11 --clone=false -``` - -**(Optional) Set the fork's "About" description:** -```bash -gh repo edit dantecmelo/libp11 \ - --description "Fork of OpenSC/libp11 adding an opt-in no_login_cache provider parameter that releases the token login session on key free to avoid session-slot exhaustion on capped HSMs (e.g. YubiHSM 2)." \ - --add-topic openssl --add-topic pkcs11 --add-topic yubihsm -``` - ---- - -## 3. Clone your fork and add the upstream remote - -A PR needs your branch to share history with OpenSC/libp11, so start from a -**clone of your fork** — **not** from your edited copy. Clone into a *new* folder: - -```bash -git clone https://github.com/dantecmelo/libp11.git -cd libp11 -git remote add upstream https://github.com/OpenSC/libp11.git -git fetch upstream -``` - -Run **all** the remaining git commands from inside this `libp11` folder. If git -prints `fatal: not a git repository`, you're in the wrong directory — `cd` back -into the clone. (Never run git inside your edited copy, and don't `git init` it.) - -> **Already have a working clone** of libp11? Reuse it instead — see **Appendix B**. - ---- - -## 4. Create your branch - -Branch off the latest upstream `main` for the cleanest PR base: - -```bash -git checkout -b session-fix upstream/main -``` - ---- - -## 5. Copy your changed files into the clone - -Copy **only the files this change touches** from your edited copy into the clone. -The PR set is the **10 source files + `NEWS`**, plus the PR README -(`README.pr.md`, which is upstream's README + the `no_login_cache` docs) copied -in **as `README.md`**. - -*Windows PowerShell:* -```powershell -$src = "C:\Users\dante.melo\My Drive\_PS\Projects\MathWorks\libp11-no-deinit-fix" -$files = @( - "src\libp11-int.h", - "src\p11_key.c", - "src\p11_slot.c", - "src\p11_rsa.c", - "src\p11_ec.c", - "src\p11_front.c", - "src\libp11.h", - "src\libp11.exports", - "src\util.h", - "src\util_uri.c", - "src\provider.c", - "src\provider_helpers.c", - "src\provider_helpers.h", - "NEWS" -) -foreach ($f in $files) { Copy-Item -Path (Join-Path $src $f) -Destination $f -Force } -Copy-Item -Path (Join-Path $src "README.pr.md") -Destination "README.md" -Force -``` - -*macOS / Linux / WSL / Git Bash:* -```bash -SRC="/path/to/libp11-no-deinit-fix" -for f in src/libp11-int.h src/p11_key.c src/p11_slot.c src/p11_rsa.c src/p11_ec.c \ - src/p11_front.c src/libp11.h src/libp11.exports src/util.h \ - src/util_uri.c src/provider.c src/provider_helpers.c src/provider_helpers.h NEWS; do - cp "$SRC/$f" "$f" -done -cp "$SRC/README.pr.md" README.md # PR README = upstream README + no_login_cache docs -``` - -> The fork-only files (`README.upstream.md`, `README.pr.md`, `FORK-AND-PR.md`, -> and your fork's own `README.md` with its fork note) are intentionally **not** -> copied into the clone — they don't belong in an upstream PR. To host a custom -> README on your fork separately, see **Appendix C**. - ---- - -## 6. Verify the diff is exactly your change - -```bash -git status -git diff --stat -``` - -Expected: the 10 `src/…` files, `NEWS`, and `README.md`. Spot-check the README: -```bash -git diff README.md # ONLY the no_login_cache bullet + the env-var line -``` -If `git diff` shows a "Fork note", an HTML comment, or `README.upstream.md` / -`README.pr.md`, those slipped in — remove them. If files you never touched show -changes, your copy is based on an older upstream — re-apply just your edits by -hand until the diff is clean. - ---- - -## 7. (Optional) Build to confirm it compiles - -**libp11 has no code-generation step for any file in this change** — unlike some -OpenSSL projects, you do **not** regenerate anything. The only build-system -touchpoint is the symbol exports list `src/libp11.exports`, which already -contains the new `PKCS11_set_no_login_cache` symbol. So you can commit as-is. - -To sanity-check that it builds (needs the toolchain from step 1; this is a fresh -clone, so generate `configure` first): - -```bash -./bootstrap # generate ./configure (git checkout, not a tarball) -./configure -make -# confirm the new public symbol is exported: -nm -D src/.libs/libp11.so | grep PKCS11_set_no_login_cache -``` - ---- - -## 8. Commit your change - -```bash -git add -A -git status # review exactly what's staged -git commit -s -m "Add opt-in no_login_cache to release the token login session on key free - -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." -``` - -The `-s` adds a `Signed-off-by` line (the **DCO** — Developer Certificate of -Origin). Check the repo's `CONTRIBUTING`/`COPYING` for whether it's required; -include `-s` if in doubt. - ---- - -## 9. Push to your fork - -```bash -git push -u origin session-fix -``` - -At the HTTPS prompt, enter username `dantecmelo` and paste a **Personal Access -Token as the password** — **your account password will not work.** - -> **If you see `Invalid username or token. Password authentication is not -> supported…` / `Authentication failed`:** -> -> 1. **Create a token** — fine-grained (recommended): -> → **Generate new token** → -> *Resource owner* `dantecmelo` → *Repository access* → only -> `dantecmelo/libp11` → *Permissions → Contents: Read and write* → -> **Generate**, then copy it (`github_pat_…`). Classic alternative: -> → "Generate new token (classic)" → -> scope `repo`. -> 2. **Push again** and paste the token at the `Password:` prompt (input hidden). -> 3. **Avoid re-prompts:** `git config --global credential.helper store` (caches -> the PAT in `~/.git-credentials`, plaintext), or use the GitHub CLI: -> `gh auth login && gh auth setup-git`. -> -> **SSH alternative:** add an SSH key (step 1), then -> `git remote set-url origin git@github.com:dantecmelo/libp11.git` and push. - -Your branch is now at `https://github.com/dantecmelo/libp11/tree/session-fix`. - ---- - -## 10. Open the pull request - -**Web UI:** visit your fork — GitHub shows a **"Compare & pull request"** banner -for the new branch; click it (otherwise **Contribute → Open pull request**). -Confirm the direction: -- **base:** `OpenSC/libp11` · `main` -- **head:** `dantecmelo/libp11` · `session-fix` - -Leave **"Allow edits by maintainers"** checked, add a title and description -(template below), then **Create pull request** (or the dropdown's **Create draft -pull request** to let CI run first). - -**Or GitHub CLI:** -```bash -gh pr create --repo OpenSC/libp11 --base main \ - --head dantecmelo:session-fix \ - --title "Add opt-in no_login_cache to release the token login session on key free" \ - --body-file PR-BODY.md -# add --draft to open it as a draft -``` - -**Suggested PR description:** -```markdown -## Problem -Signing with one short-lived `openssl` process per file against a token with a -hard limit on concurrent authenticated sessions (notably the YubiHSM 2, max 16) -exhausts the sessions. Each process logs in and the login session is never -released, because libp11 skips its teardown at process exit — the -`g_shutdown_mode` guard set by the `atexit` handler in `util_uri.c` makes -`UTIL_CTX_free_libp11` skip `PKCS11_release_all_slots`/`C_Finalize`. On the -YubiHSM, `C_CloseSession` does not free the authenticated session — only -`C_Logout` does. After 16 files the 17th fails with "could not read private key". - -## Change -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 (in the RSA/EC key -`finish` callbacks), during healthy runtime — not at the skipped exit. Re-login -happens automatically on the next key load. - -## Safety / scope -- Opt-in: with the parameter unset, behaviour is byte-for-byte unchanged. -- The logout helper is self-contained (it does not call `pkcs11_wipe_cache` or - `pkcs11_get_session`) to avoid re-entrancy from the key-free callback; it is - idempotent via a lockless `logged_in` check, so it never recurses or nests - `slot->lock`. -- Covers RSA and EC keys. EdDSA has no per-key finish callback in libp11, so it - is out of scope (documented). - -## Testing -Verified on a YubiHSM 2: a 50-iteration `openssl dgst -sha512 -sign` loop that -previously failed after the 16th now completes; a PKCS#11 trace shows one -`C_Logout` per key free. -``` - ---- - -## 11. After opening the PR - -- **Respond to review** by pushing follow-up commits to the **same branch** - (`git push origin session-fix`) — the PR updates automatically. -- **CI** runs on the PR; watch the checks and fix any failures. -- **Likely requests / things already done for this change:** - - A test under `tests/` exercising the option (maintainers may ask for one). - - **`NEWS` entry** — already added (top "unreleased" section). - - **Doxygen comment** on the new public function — already added in `libp11.h`. - - **Exported symbol** — already added to `src/libp11.exports`. - - A possible design discussion about exposing a new public API - (`PKCS11_set_no_login_cache`) vs keeping the flag internal. It is exposed - publicly because `util_uri.c` already configures libp11 through public - `PKCS11_*` calls (e.g. `PKCS11_set_ui_method`); that's the consistent layering. - - A DCO `Signed-off-by` line if not already present. - ---- - -## 12. Keep your fork up to date - -If upstream moves on, rebase your branch onto the latest `main`: - -```bash -git fetch upstream -git checkout main && git merge --ff-only upstream/main && git push origin main -git checkout session-fix -git rebase main # replay your commits on top; resolve conflicts -git push --force-with-lease origin session-fix -``` - -`--force-with-lease` updates the PR branch safely after a rebase without -clobbering anyone else's pushed changes. - ---- - -## Appendix A — Publish your fork without a PR - -If you just want your code on your own fork (no upstream PR), turn your edited -copy directly into a repo. Its history won't link to upstream — fine for a -personal copy, but no clean PR later (use the main flow for that). - -```bash -cd "/path/to/libp11-no-deinit-fix" # your edited copy -git init -git remote add origin https://github.com/dantecmelo/libp11.git -git add . # the bundled .gitignore keeps build junk out -git commit -m "no_login_cache: release the token login session on key free" -git branch -M session-fix -git push -u origin session-fix # PAT as password — see step 9 -``` - -Push to a **new branch** as shown, **not** `main` (your fork's `main` holds -upstream history; pushing unrelated history there would be rejected). - ---- - -## Appendix B — Reuse an existing clone - -If you already have a clone of libp11, use it instead of cloning fresh in step 3. -Point its remotes at your fork: - -```bash -cd /path/to/your/libp11-clone -git remote rename origin upstream # was OpenSC/libp11 → now "upstream" -git remote add origin https://github.com/dantecmelo/libp11.git -git fetch upstream -git remote -v # upstream=OpenSC, origin=your fork -``` - -Then continue at **step 4**. - -**Linux ownership note:** if the clone was built under `sudo` and is root-owned, -git will refuse to use it. Fix once: `sudo chown -R "$USER":"$USER" ` (or -`git config --global --add safe.directory `). - ---- - -## Appendix C — Host a custom README / handle the fork-only files - -Four files are **fork-only** and must stay **out of the PR**: -`README.upstream.md` (pristine upstream copy), `README.pr.md` (the PR README), -this `FORK-AND-PR.md`, and your edited copy's `README.md` (the one with the fork -note). The PR's `README.md` comes from `README.pr.md` (step 5), so don't copy the -others into the PR branch. - -To show a custom README on your **fork's** page, commit the fork files to a -different branch so they never mix into the PR — e.g. on your fork's `main`: -```bash -git checkout main -# copy README.md (fork version), README.upstream.md, FORK-AND-PR.md in, then: -git add README.md README.upstream.md FORK-AND-PR.md -git commit -m "Fork-specific docs" -git push origin main -git checkout session-fix # back to your PR branch -``` - ---- - -## Quick reference - -| Action | Command | -|---|---| -| Create fork (CLI) | `gh repo fork OpenSC/libp11 --clone=false` | -| Clone fork + upstream | `git clone https://github.com/dantecmelo/libp11.git && cd libp11 && git remote add upstream https://github.com/OpenSC/libp11.git` | -| Create branch | `git checkout -b session-fix upstream/main` | -| Build (sanity check) | `./bootstrap && ./configure && make` | -| Commit (with DCO) | `git commit -s -m "…"` | -| Push to fork | `git push -u origin session-fix` | -| Open PR (CLI) | `gh pr create --repo OpenSC/libp11 --base main --head dantecmelo:session-fix` | -| Sync with upstream | `git fetch upstream && git rebase upstream/main` |