Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
@@ -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)

Expand Down
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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

Expand Down
11 changes: 11 additions & 0 deletions src/libp11-int.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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;
Expand Down Expand Up @@ -262,6 +264,12 @@ 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 *);

/* 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);

Expand Down Expand Up @@ -315,6 +323,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);
Expand Down
1 change: 1 addition & 0 deletions src/libp11.exports
Original file line number Diff line number Diff line change
Expand Up @@ -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
16 changes: 16 additions & 0 deletions src/libp11.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
*
Expand Down
7 changes: 7 additions & 0 deletions src/p11_ec.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
8 changes: 8 additions & 0 deletions src/p11_front.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
34 changes: 34 additions & 0 deletions src/p11_key.c
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,40 @@ 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;
}
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)
Expand Down
7 changes: 7 additions & 0 deletions src/p11_rsa.c
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
61 changes: 61 additions & 0 deletions src/p11_slot.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand Down Expand Up @@ -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;
Expand Down
3 changes: 3 additions & 0 deletions src/provider.c
Original file line number Diff line number Diff line change
Expand Up @@ -423,6 +423,9 @@ 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)
{
/* 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);
}

Expand Down
46 changes: 46 additions & 0 deletions src/provider_helpers.c
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
*/

#include "provider_helpers.h"
#include "libp11-int.h" /* pkcs11_release_login_for_pkey() */
#include <ctype.h> /* isdigit() */

#if defined(__GNUC__) || defined(__clang__)
Expand All @@ -45,6 +46,7 @@ typedef struct {
char *pin;
char *debug_level;
char *force_login;
char *no_login_cache;
char *init_args;
} PROVIDER_PARAMS;

Expand All @@ -65,8 +67,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;
Expand Down Expand Up @@ -240,6 +244,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);
}
Expand Down Expand Up @@ -283,6 +288,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
};
Expand Down Expand Up @@ -324,6 +330,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);
}
Expand Down Expand Up @@ -383,6 +392,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;
}

Expand Down Expand Up @@ -512,6 +532,21 @@ 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)
{
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)
{
Expand Down Expand Up @@ -1662,6 +1697,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);
}
}

/*
Expand All @@ -1678,6 +1718,7 @@ static int PROVIDER_CTX_get_specific_parameters(PROVIDER_CTX *prov_ctx)
{"pin", OSSL_PARAM_UTF8_PTR, &params.pin, 0, 0},
{"debug_level", OSSL_PARAM_UTF8_PTR, &params.debug_level, 0, 0},
{"force_login", OSSL_PARAM_UTF8_PTR, &params.force_login, 0, 0},
{"no_login_cache", OSSL_PARAM_UTF8_PTR, &params.no_login_cache, 0, 0},
{"init_args", OSSL_PARAM_UTF8_PTR, &params.init_args, 0, 0},
OSSL_PARAM_END
};
Expand Down Expand Up @@ -1712,6 +1753,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);
Expand Down
Loading