use core::fmt;
use std::error::Error;
use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{Arc, Condvar, Mutex, MutexGuard, PoisonError};
use cryptoki::context::{CInitializeArgs, CInitializeFlags, Pkcs11};
use cryptoki::error::{Error as CryptokiError, RvError};
use cryptoki::mechanism::eddsa::{EddsaParams, EddsaSignatureScheme};
use cryptoki::mechanism::rsa::{PkcsMgfType, PkcsPssParams};
use cryptoki::mechanism::{Mechanism, MechanismType};
use cryptoki::object::{Attribute, AttributeType, KeyType as CkKeyType, ObjectClass, ObjectHandle};
use cryptoki::session::{Session, UserType};
use cryptoki::slot::Slot;
use cryptoki::types::AuthPin;
use sha2::{Digest as _, Sha256};
use super::{KeyId, KeySpec, KeyStore, KeyStoreCapabilities, KeyStoreError};
use crate::jose::{Jwk, JwsAlgorithm, KeyType, normalize_ecdsa_signature};
const P256_PARAMS: &[u8] = &[0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07];
const SECP256K1_PARAMS: &[u8] = &[0x06, 0x05, 0x2b, 0x81, 0x04, 0x00, 0x0a];
const ED25519_PARAMS: &[u8] = &[0x06, 0x03, 0x2b, 0x65, 0x70];
const ED25519_NAME_PARAMS: &[u8] = b"\x13\x0cedwards25519";
pub const STAGING_LABEL_PREFIX: &str = "identity-kit-staging-";
const RSA_PUBLIC_EXPONENT: &[u8] = &[0x01, 0x00, 0x01];
const SHA256_DIGEST_INFO_PREFIX: &[u8] = &[
0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05,
0x00, 0x04, 0x20,
];
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum Pkcs11Token {
Label(String),
Slot(u64),
}
impl From<&str> for Pkcs11Token {
fn from(label: &str) -> Self {
Self::Label(label.to_owned())
}
}
impl From<String> for Pkcs11Token {
fn from(label: String) -> Self {
Self::Label(label)
}
}
impl From<u64> for Pkcs11Token {
fn from(slot: u64) -> Self {
Self::Slot(slot)
}
}
impl fmt::Display for Pkcs11Token {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Label(label) => write!(f, "token `{label}`"),
Self::Slot(slot) => write!(f, "slot {slot}"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct Pkcs11KeyStoreOptions {
pub sessions: usize,
pub rsa_algorithm: JwsAlgorithm,
pub algorithms: Option<Vec<JwsAlgorithm>>,
}
impl Default for Pkcs11KeyStoreOptions {
fn default() -> Self {
Self {
sessions: 4,
rsa_algorithm: JwsAlgorithm::RS256,
algorithms: None,
}
}
}
impl Pkcs11KeyStoreOptions {
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn sessions(mut self, sessions: usize) -> Self {
self.sessions = sessions;
self
}
#[must_use]
pub fn rsa_algorithm(mut self, alg: JwsAlgorithm) -> Self {
self.rsa_algorithm = alg;
self
}
#[must_use]
pub fn algorithms(mut self, algorithms: impl IntoIterator<Item = JwsAlgorithm>) -> Self {
self.algorithms = Some(algorithms.into_iter().collect());
self
}
}
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum Pkcs11Error {
#[error("PKCS#11 module `{}`: {source}", module.display())]
Module {
module: PathBuf,
#[source]
source: CryptokiError,
},
#[error("PKCS#11 module has no {0}")]
TokenNotFound(Pkcs11Token),
#[error("PKCS#11 key store options: {0}")]
InvalidOptions(String),
#[error("PKCS#11 {function} failed: {source}")]
Call {
function: &'static str,
#[source]
source: CryptokiError,
},
#[error("PKCS#11 module returned a {actual}-byte signature for {alg}, expected {expected}")]
SignatureLength {
alg: JwsAlgorithm,
expected: usize,
actual: usize,
},
}
impl Pkcs11Error {
fn call(function: &'static str) -> impl FnOnce(CryptokiError) -> KeyStoreError {
move |source| KeyStoreError::backend(Self::Call { function, source })
}
fn closes_session(&self) -> bool {
matches!(
self,
Self::Call {
source: CryptokiError::Pkcs11(
RvError::SessionHandleInvalid
| RvError::SessionClosed
| RvError::DeviceRemoved
| RvError::DeviceError
| RvError::TokenNotPresent,
_
),
..
}
)
}
}
#[derive(Clone)]
pub struct Pkcs11KeyStore {
inner: Arc<Inner>,
}
struct Inner {
module: PathBuf,
context: Pkcs11,
slot: Slot,
token_label: String,
pin: Option<AuthPin>,
options: Pkcs11KeyStoreOptions,
algorithms: Vec<JwsAlgorithm>,
key_pair_generation: Vec<MechanismType>,
write_protected: bool,
pool: Pool,
writes: Arc<Mutex<()>>,
}
struct Pool {
state: Mutex<PoolState>,
available: Condvar,
max: usize,
}
#[derive(Default)]
struct PoolState {
idle: Vec<Session>,
open: usize,
}
impl Pkcs11KeyStore {
pub fn open(
module: impl AsRef<Path>,
token: impl Into<Pkcs11Token>,
pin: Option<&str>,
) -> Result<Self, KeyStoreError> {
Self::open_with(module, token, pin, Pkcs11KeyStoreOptions::default())
}
pub fn open_with(
module: impl AsRef<Path>,
token: impl Into<Pkcs11Token>,
pin: Option<&str>,
options: Pkcs11KeyStoreOptions,
) -> Result<Self, KeyStoreError> {
let module = module.as_ref().to_path_buf();
let token = token.into();
if options.sessions == 0 {
return Err(invalid_options("`sessions` must be at least 1"));
}
if !matches!(
options.rsa_algorithm,
JwsAlgorithm::RS256 | JwsAlgorithm::PS256
) {
return Err(invalid_options(format!(
"`rsa_algorithm` must be RS256 or PS256, not {}",
options.rsa_algorithm
)));
}
let context = load_module(&module)?;
let (slot, token_label, write_protected) = find_token(&context, &token)?;
let mechanisms = context
.get_mechanism_list(slot)
.map_err(Pkcs11Error::call("C_GetMechanismList"))?;
let mut algorithms = Vec::new();
if mechanisms.contains(&MechanismType::EDDSA) {
algorithms.push(JwsAlgorithm::EdDSA);
}
if mechanisms.contains(&MechanismType::ECDSA) {
algorithms.push(JwsAlgorithm::ES256);
#[cfg(feature = "secp256k1")]
algorithms.push(JwsAlgorithm::ES256K);
}
let rsa_mechanism = match options.rsa_algorithm {
JwsAlgorithm::PS256 => MechanismType::RSA_PKCS_PSS,
_ => MechanismType::RSA_PKCS,
};
if mechanisms.contains(&rsa_mechanism) {
algorithms.push(options.rsa_algorithm);
}
if let Some(wanted) = &options.algorithms {
algorithms.retain(|alg| wanted.contains(alg));
}
let key_pair_generation = [
MechanismType::ECC_EDWARDS_KEY_PAIR_GEN,
MechanismType::ECC_KEY_PAIR_GEN,
MechanismType::RSA_PKCS_KEY_PAIR_GEN,
]
.into_iter()
.filter(|mechanism| mechanisms.contains(mechanism))
.collect();
let writes = token_writes(&module, slot.id());
let inner = Inner {
writes,
module,
context,
slot,
token_label,
pin: pin.map(|pin| AuthPin::from(pin.to_owned())),
pool: Pool {
state: Mutex::new(PoolState::default()),
available: Condvar::new(),
max: options.sessions,
},
options,
algorithms,
key_pair_generation,
write_protected,
};
let session = inner.open_session()?;
inner.pool.release(session);
Ok(Self {
inner: Arc::new(inner),
})
}
pub fn module(&self) -> &Path {
&self.inner.module
}
pub fn slot(&self) -> u64 {
self.inner.slot.id()
}
pub fn token_label(&self) -> &str {
&self.inner.token_label
}
pub fn options(&self) -> &Pkcs11KeyStoreOptions {
&self.inner.options
}
async fn blocking<T, F>(&self, f: F) -> Result<T, KeyStoreError>
where
T: Send + 'static,
F: FnOnce(&Inner) -> Result<T, KeyStoreError> + Send + 'static,
{
let inner = Arc::clone(&self.inner);
match tokio::task::spawn_blocking(move || f(&inner)).await {
Ok(result) => result,
Err(e) if e.is_panic() => std::panic::resume_unwind(e.into_panic()),
Err(e) => Err(KeyStoreError::backend(e)),
}
}
}
impl fmt::Debug for Pkcs11KeyStore {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Pkcs11KeyStore")
.field("module", &self.inner.module)
.field("slot", &self.inner.slot.id())
.field("token_label", &self.inner.token_label)
.field("algorithms", &self.inner.algorithms)
.field("options", &self.inner.options)
.finish_non_exhaustive()
}
}
#[async_trait::async_trait]
impl KeyStore for Pkcs11KeyStore {
fn capabilities(&self) -> KeyStoreCapabilities {
let capabilities =
KeyStoreCapabilities::new(self.inner.algorithms.iter().copied()).remote();
if self.inner.write_protected {
capabilities
} else {
capabilities.with_generate().with_delete()
}
}
async fn public_jwk(&self, key: &KeyId) -> Result<Jwk, KeyStoreError> {
let key = key.clone();
self.blocking(move |inner| inner.with_session(|session| inner.public_jwk(session, &key)))
.await
}
async fn sign(
&self,
key: &KeyId,
alg: JwsAlgorithm,
data: &[u8],
) -> Result<Vec<u8>, KeyStoreError> {
let key = key.clone();
let data = data.to_vec();
self.blocking(move |inner| {
inner.with_session(|session| inner.sign(session, &key, alg, &data))
})
.await
}
async fn exists(&self, key: &KeyId) -> Result<bool, KeyStoreError> {
let key = key.clone();
self.blocking(move |inner| {
inner.with_session(|session| {
Ok(find_key(session, ObjectClass::PRIVATE_KEY, &key)?.is_some())
})
})
.await
}
async fn generate(&self, spec: KeySpec) -> Result<KeyId, KeyStoreError> {
if self.inner.write_protected {
return Err(KeyStoreError::Unsupported("generate"));
}
self.blocking(move |inner| {
let _writing = inner.writes.lock().unwrap_or_else(PoisonError::into_inner);
inner.with_session(|session| inner.generate(session, &spec))
})
.await
}
async fn delete(&self, key: &KeyId) -> Result<(), KeyStoreError> {
if self.inner.write_protected {
return Err(KeyStoreError::Unsupported("delete"));
}
let key = key.clone();
self.blocking(move |inner| {
let _writing = inner.writes.lock().unwrap_or_else(PoisonError::into_inner);
inner.with_session(|session| delete(session, &key))
})
.await
}
}
impl Inner {
fn open_session(&self) -> Result<Session, KeyStoreError> {
let session = if self.write_protected {
self.context.open_ro_session(self.slot)
} else {
self.context.open_rw_session(self.slot)
}
.map_err(Pkcs11Error::call("C_OpenSession"))?;
if let Some(pin) = &self.pin {
match session.login(UserType::User, Some(pin)) {
Ok(()) | Err(CryptokiError::Pkcs11(RvError::UserAlreadyLoggedIn, _)) => {}
Err(source) => return Err(Pkcs11Error::call("C_Login")(source)),
}
}
Ok(session)
}
fn with_session<T>(
&self,
f: impl FnOnce(&Session) -> Result<T, KeyStoreError>,
) -> Result<T, KeyStoreError> {
let session = self.pool.acquire(|| self.open_session())?;
let result = f(&session);
let dead = matches!(
&result,
Err(KeyStoreError::Backend(e))
if e.downcast_ref::<Pkcs11Error>().is_some_and(Pkcs11Error::closes_session)
);
if dead {
self.pool.discard(session);
} else {
self.pool.release(session);
}
result
}
fn algorithm_of(&self, kind: KeyKind) -> JwsAlgorithm {
match kind {
KeyKind::Ed25519 => JwsAlgorithm::EdDSA,
KeyKind::P256 => JwsAlgorithm::ES256,
KeyKind::Secp256k1 => JwsAlgorithm::ES256K,
KeyKind::Rsa => self.options.rsa_algorithm,
}
}
fn public_jwk(&self, session: &Session, key: &KeyId) -> Result<Jwk, KeyStoreError> {
let Some(public) = find_key(session, ObjectClass::PUBLIC_KEY, key)? else {
return Err(
if find_key(session, ObjectClass::PRIVATE_KEY, key)?.is_some() {
KeyStoreError::invalid_key(
key.clone(),
"the token has no public key object with this label",
)
} else {
KeyStoreError::NotFound(key.clone())
},
);
};
self.public_jwk_of(session, key, public)
}
fn public_jwk_of(
&self,
session: &Session,
key: &KeyId,
public: ObjectHandle,
) -> Result<Jwk, KeyStoreError> {
let kind = key_kind(session, key, public)?;
let invalid = |reason: &str| KeyStoreError::invalid_key(key.clone(), reason);
let not_a_jwk = |error: crate::jose::JwkError| KeyStoreError::InvalidKey {
key: key.clone(),
reason: "the public key does not convert to a JWK".to_owned(),
source: Some(Box::new(error)),
};
let jwk = match kind {
KeyKind::Rsa => {
let attributes = session
.get_attributes(
public,
&[AttributeType::Modulus, AttributeType::PublicExponent],
)
.map_err(Pkcs11Error::call("C_GetAttributeValue"))?;
let mut modulus = None;
let mut exponent = None;
for attribute in attributes {
match attribute {
Attribute::Modulus(n) => modulus = Some(n),
Attribute::PublicExponent(e) => exponent = Some(e),
_ => {}
}
}
let (Some(n), Some(e)) = (modulus, exponent) else {
return Err(invalid(
"the public key object has no modulus or public exponent",
));
};
Jwk::from_rsa_public(&n, &e).map_err(not_a_jwk)?
}
KeyKind::Ed25519 | KeyKind::P256 | KeyKind::Secp256k1 => {
let point = ec_point(session, key, public, kind)?;
match kind {
KeyKind::Ed25519 => Jwk::from_ed25519_public(&point).map_err(not_a_jwk)?,
KeyKind::P256 => Jwk::from_p256_public(&point).map_err(not_a_jwk)?,
#[cfg(feature = "secp256k1")]
KeyKind::Secp256k1 => Jwk::from_secp256k1_public(&point).map_err(not_a_jwk)?,
#[cfg(not(feature = "secp256k1"))]
KeyKind::Secp256k1 => {
return Err(invalid("secp256k1 keys need the `secp256k1` feature"));
}
KeyKind::Rsa => unreachable!("matched above"),
}
}
};
Ok(jwk
.with_kid(key.as_str())
.with_alg(self.algorithm_of(kind).as_str()))
}
fn sign(
&self,
session: &Session,
key: &KeyId,
alg: JwsAlgorithm,
data: &[u8],
) -> Result<Vec<u8>, KeyStoreError> {
let private = find_key(session, ObjectClass::PRIVATE_KEY, key)?
.ok_or_else(|| KeyStoreError::NotFound(key.clone()))?;
let kind = key_kind(session, key, private)?;
let key_alg = self.algorithm_of(kind);
if alg != key_alg {
return Err(KeyStoreError::AlgorithmMismatch {
key: key.clone(),
key_alg,
requested: alg,
});
}
if !self.algorithms.contains(&alg) {
return Err(KeyStoreError::UnsupportedAlgorithm(alg));
}
let signature = match kind {
KeyKind::Ed25519 => session
.sign(
&Mechanism::Eddsa(EddsaParams::new(EddsaSignatureScheme::Pure)),
private,
data,
)
.map_err(Pkcs11Error::call("C_Sign"))?,
KeyKind::P256 => session
.sign(&Mechanism::Ecdsa, private, &Sha256::digest(data))
.map_err(Pkcs11Error::call("C_Sign"))?,
KeyKind::Secp256k1 => {
let signature = session
.sign(&Mechanism::Ecdsa, private, &Sha256::digest(data))
.map_err(Pkcs11Error::call("C_Sign"))?;
normalize_ecdsa_signature(alg, &signature).unwrap_or(signature)
}
KeyKind::Rsa if alg == JwsAlgorithm::PS256 => {
let params = PkcsPssParams {
hash_alg: MechanismType::SHA256,
mgf: PkcsMgfType::MGF1_SHA256,
s_len: 32.into(),
};
session
.sign(
&Mechanism::RsaPkcsPss(params),
private,
&Sha256::digest(data),
)
.map_err(Pkcs11Error::call("C_Sign"))?
}
KeyKind::Rsa => {
let mut digest_info = SHA256_DIGEST_INFO_PREFIX.to_vec();
digest_info.extend_from_slice(&Sha256::digest(data));
session
.sign(&Mechanism::RsaPkcs, private, &digest_info)
.map_err(Pkcs11Error::call("C_Sign"))?
}
};
if let Some(expected) = match kind {
KeyKind::Ed25519 | KeyKind::P256 | KeyKind::Secp256k1 => Some(64),
KeyKind::Rsa => None,
} && signature.len() != expected
{
return Err(KeyStoreError::backend(Pkcs11Error::SignatureLength {
alg,
expected,
actual: signature.len(),
}));
}
Ok(signature)
}
#[expect(
clippy::too_many_lines,
reason = "one template per key type, kept side by side to compare"
)]
fn generate(&self, session: &Session, spec: &KeySpec) -> Result<KeyId, KeyStoreError> {
spec.validate()?;
if !self.algorithms.contains(&spec.alg) {
return Err(KeyStoreError::UnsupportedAlgorithm(spec.alg));
}
let (mechanism, generation, mut public_template) = match spec.key_type {
KeyType::Ed25519 => (
Mechanism::EccEdwardsKeyPairGen,
MechanismType::ECC_EDWARDS_KEY_PAIR_GEN,
vec![
Attribute::KeyType(CkKeyType::EC_EDWARDS),
Attribute::EcParams(ED25519_PARAMS.to_vec()),
],
),
KeyType::P256 => (
Mechanism::EccKeyPairGen,
MechanismType::ECC_KEY_PAIR_GEN,
vec![
Attribute::KeyType(CkKeyType::EC),
Attribute::EcParams(P256_PARAMS.to_vec()),
],
),
KeyType::Secp256k1 => (
Mechanism::EccKeyPairGen,
MechanismType::ECC_KEY_PAIR_GEN,
vec![
Attribute::KeyType(CkKeyType::EC),
Attribute::EcParams(SECP256K1_PARAMS.to_vec()),
],
),
KeyType::Rsa2048 | KeyType::Rsa4096 => {
let bits: cryptoki::types::Ulong = match spec.key_type {
KeyType::Rsa4096 => 4096.into(),
_ => 2048.into(),
};
(
Mechanism::RsaPkcsKeyPairGen,
MechanismType::RSA_PKCS_KEY_PAIR_GEN,
vec![
Attribute::KeyType(CkKeyType::RSA),
Attribute::ModulusBits(bits),
Attribute::PublicExponent(RSA_PUBLIC_EXPONENT.to_vec()),
],
)
}
};
if !self.key_pair_generation.contains(&generation) {
return Err(KeyStoreError::UnsupportedKeyType(spec.key_type));
}
public_template.extend([
Attribute::Token(true),
Attribute::Private(false),
Attribute::Verify(true),
]);
let mut private_template = vec![
Attribute::Token(true),
Attribute::Private(true),
Attribute::Sensitive(true),
Attribute::Extractable(false),
Attribute::Sign(true),
];
if let Some(label) = &spec.label {
let id = KeyId::new(label.as_str());
if find_key(session, ObjectClass::PRIVATE_KEY, &id)?.is_some()
|| find_key(session, ObjectClass::PUBLIC_KEY, &id)?.is_some()
{
return Err(KeyStoreError::AlreadyExists(id));
}
public_template.extend(label_attributes(label));
private_template.extend(label_attributes(label));
} else {
let staging = staging_label();
public_template.extend(label_attributes(&staging));
private_template.extend(label_attributes(&staging));
}
let (public, private) = session
.generate_key_pair(&mechanism, &public_template, &private_template)
.map_err(Pkcs11Error::call("C_GenerateKeyPair"))?;
if let Some(label) = &spec.label {
return Ok(KeyId::new(label.as_str()));
}
let labelled = self
.public_jwk_of(session, &KeyId::new(""), public)
.and_then(|jwk| {
jwk.thumbprint_sha256()
.map_err(|e| KeyStoreError::InvalidKey {
key: KeyId::new(""),
reason: "no RFC 7638 thumbprint".to_owned(),
source: Some(Box::new(e)),
})
})
.and_then(|thumbprint| {
let attributes = label_attributes(&thumbprint);
session
.update_attributes(public, &attributes)
.and_then(|()| session.update_attributes(private, &attributes))
.map_err(Pkcs11Error::call("C_SetAttributeValue"))?;
Ok(KeyId::new(thumbprint))
});
if labelled.is_err() {
let _ = session.destroy_object(private);
let _ = session.destroy_object(public);
}
labelled
}
}
impl Pool {
fn acquire(
&self,
open: impl FnOnce() -> Result<Session, KeyStoreError>,
) -> Result<Session, KeyStoreError> {
let mut state = self.lock();
loop {
if let Some(session) = state.idle.pop() {
return Ok(session);
}
if state.open < self.max {
state.open += 1;
drop(state);
return open().inspect_err(|_| {
self.lock().open -= 1;
self.available.notify_one();
});
}
state = self
.available
.wait(state)
.unwrap_or_else(PoisonError::into_inner);
}
}
fn release(&self, session: Session) {
self.lock().idle.push(session);
self.available.notify_one();
}
fn discard(&self, session: Session) {
drop(session);
self.lock().open -= 1;
self.available.notify_one();
}
fn lock(&self) -> MutexGuard<'_, PoolState> {
self.state.lock().unwrap_or_else(PoisonError::into_inner)
}
}
static MODULES: Mutex<Vec<(PathBuf, Pkcs11)>> = Mutex::new(Vec::new());
static TOKEN_WRITES: Mutex<Vec<TokenWriteLock>> = Mutex::new(Vec::new());
type TokenWriteLock = ((PathBuf, u64), Arc<Mutex<()>>);
fn token_writes(module: &Path, slot: u64) -> Arc<Mutex<()>> {
let key = (
std::fs::canonicalize(module).unwrap_or_else(|_| module.to_path_buf()),
slot,
);
let mut locks = TOKEN_WRITES.lock().unwrap_or_else(PoisonError::into_inner);
if let Some((_, lock)) = locks.iter().find(|(k, _)| *k == key) {
return Arc::clone(lock);
}
let lock = Arc::new(Mutex::new(()));
locks.push((key, Arc::clone(&lock)));
lock
}
fn load_module(module: &Path) -> Result<Pkcs11, KeyStoreError> {
let key = std::fs::canonicalize(module).unwrap_or_else(|_| module.to_path_buf());
let mut modules = MODULES.lock().unwrap_or_else(PoisonError::into_inner);
if let Some((_, context)) = modules.iter().find(|(path, _)| *path == key) {
return Ok(context.clone());
}
let failed = |source| {
KeyStoreError::backend(Pkcs11Error::Module {
module: module.to_path_buf(),
source,
})
};
let context = Pkcs11::new(module).map_err(failed)?;
match context.initialize(CInitializeArgs::new(CInitializeFlags::OS_LOCKING_OK)) {
Ok(()) | Err(CryptokiError::Pkcs11(RvError::CryptokiAlreadyInitialized, _)) => {}
Err(source) => return Err(failed(source)),
}
modules.push((key, context.clone()));
Ok(context)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum KeyKind {
Ed25519,
P256,
Secp256k1,
Rsa,
}
fn find_token(
context: &Pkcs11,
token: &Pkcs11Token,
) -> Result<(Slot, String, bool), KeyStoreError> {
let not_found = || KeyStoreError::backend(Pkcs11Error::TokenNotFound(token.clone()));
let slots = context
.get_slots_with_token()
.map_err(Pkcs11Error::call("C_GetSlotList"))?;
for slot in slots {
let matches = match token {
Pkcs11Token::Slot(id) => slot.id() == *id,
Pkcs11Token::Label(_) => true,
};
if !matches {
continue;
}
let info = context
.get_token_info(slot)
.map_err(Pkcs11Error::call("C_GetTokenInfo"))?;
let label = info.label().trim_end().to_owned();
if let Pkcs11Token::Label(wanted) = token
&& label != wanted.trim_end()
{
continue;
}
return Ok((slot, label, info.write_protected()));
}
Err(not_found())
}
fn find_key(
session: &Session,
class: ObjectClass,
key: &KeyId,
) -> Result<Option<ObjectHandle>, KeyStoreError> {
let template = [
Attribute::Class(class),
Attribute::Label(key.as_str().as_bytes().to_vec()),
];
let objects = session
.find_objects(&template)
.map_err(Pkcs11Error::call("C_FindObjects"))?;
match objects.as_slice() {
[] => Ok(None),
[object] => Ok(Some(*object)),
several => Err(KeyStoreError::invalid_key(
key.clone(),
format!(
"the label names {} objects of the same class",
several.len()
),
)),
}
}
fn delete(session: &Session, key: &KeyId) -> Result<(), KeyStoreError> {
let private = find_key(session, ObjectClass::PRIVATE_KEY, key)?;
let public = find_key(session, ObjectClass::PUBLIC_KEY, key)?;
if private.is_none() && public.is_none() {
return Err(KeyStoreError::NotFound(key.clone()));
}
let mut destroyed = Ok(());
for object in [private, public].into_iter().flatten() {
destroyed = destroyed.and(session.destroy_object(object));
}
destroyed.map_err(Pkcs11Error::call("C_DestroyObject"))
}
#[expect(
clippy::cast_possible_truncation,
reason = "the low 64 bits of the time are plenty to keep labels apart"
)]
fn staging_label() -> String {
static COUNTER: AtomicU64 = AtomicU64::new(0);
let nanos = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map_or(0, |since| since.as_nanos() as u64);
format!(
"{STAGING_LABEL_PREFIX}{nanos:016x}-{:x}",
COUNTER.fetch_add(1, Ordering::Relaxed)
)
}
fn label_attributes(label: &str) -> [Attribute; 2] {
[
Attribute::Label(label.as_bytes().to_vec()),
Attribute::Id(label.as_bytes().to_vec()),
]
}
fn key_kind(
session: &Session,
key: &KeyId,
object: ObjectHandle,
) -> Result<KeyKind, KeyStoreError> {
let attributes = session
.get_attributes(object, &[AttributeType::KeyType, AttributeType::EcParams])
.map_err(Pkcs11Error::call("C_GetAttributeValue"))?;
let mut key_type = None;
let mut params = None;
for attribute in attributes {
match attribute {
Attribute::KeyType(t) => key_type = Some(t),
Attribute::EcParams(p) => params = Some(p),
_ => {}
}
}
let invalid = |reason: &str| KeyStoreError::invalid_key(key.clone(), reason);
match key_type {
Some(CkKeyType::RSA) => Ok(KeyKind::Rsa),
Some(CkKeyType::EC) => match params.as_deref() {
Some(P256_PARAMS) => Ok(KeyKind::P256),
Some(SECP256K1_PARAMS) => Ok(KeyKind::Secp256k1),
Some(_) => Err(invalid("the curve is not P-256 or secp256k1")),
None => Err(invalid("the EC key has no CKA_EC_PARAMS")),
},
Some(CkKeyType::EC_EDWARDS) => match params.as_deref() {
Some(ED25519_PARAMS | ED25519_NAME_PARAMS) => Ok(KeyKind::Ed25519),
Some(_) => Err(invalid("the Edwards curve is not Ed25519")),
None => Err(invalid("the Edwards key has no CKA_EC_PARAMS")),
},
Some(_) => Err(invalid("the key type is not RSA, EC or EC_EDWARDS")),
None => Err(invalid("the object has no CKA_KEY_TYPE")),
}
}
fn ec_point(
session: &Session,
key: &KeyId,
object: ObjectHandle,
kind: KeyKind,
) -> Result<Vec<u8>, KeyStoreError> {
let attributes = session
.get_attributes(object, &[AttributeType::EcPoint])
.map_err(Pkcs11Error::call("C_GetAttributeValue"))?;
let invalid = |reason: &str| KeyStoreError::invalid_key(key.clone(), reason);
let Some(Attribute::EcPoint(encoded)) = attributes.into_iter().next() else {
return Err(invalid("the public key object has no CKA_EC_POINT"));
};
ec_point_bytes(&encoded, kind)
.map(<[u8]>::to_vec)
.ok_or_else(|| invalid("CKA_EC_POINT is neither a DER OCTET STRING nor a bare point"))
}
fn ec_point_bytes(encoded: &[u8], kind: KeyKind) -> Option<&[u8]> {
let fits = |point: &[u8]| match kind {
KeyKind::Ed25519 => point.len() == 32,
KeyKind::P256 | KeyKind::Secp256k1 => point.len() == 65 && point[0] == 0x04,
KeyKind::Rsa => false,
};
match der_octet_string(encoded) {
Some(content) if fits(content) => Some(content),
_ if fits(encoded) => Some(encoded),
other => other,
}
}
fn der_octet_string(der: &[u8]) -> Option<&[u8]> {
let (&0x04, rest) = der.split_first()? else {
return None;
};
let (&first, rest) = rest.split_first()?;
let (length, content) = if first < 0x80 {
(usize::from(first), rest)
} else {
let length_bytes = usize::from(first & 0x7f);
if length_bytes == 0 || length_bytes > 2 || rest.len() < length_bytes {
return None;
}
let (length, content) = rest.split_at(length_bytes);
let length = length
.iter()
.fold(0usize, |n, &b| (n << 8) | usize::from(b));
if length < 0x80 {
return None;
}
(length, content)
};
(content.len() == length).then_some(content)
}
fn invalid_options(reason: impl Into<String>) -> KeyStoreError {
KeyStoreError::backend(Pkcs11Error::InvalidOptions(reason.into()))
}
const _: () = {
const fn assert_send_sync<T: Send + Sync + Error + 'static>() {}
assert_send_sync::<Pkcs11Error>();
};
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn der_octet_strings_decode() {
assert_eq!(
der_octet_string(&[0x04, 0x02, 0xaa, 0xbb]),
Some(&[0xaa, 0xbb][..])
);
assert_eq!(der_octet_string(&[0x04, 0x00]), Some(&[][..]));
let mut long = vec![0x04, 0x81, 0x80];
long.extend(std::iter::repeat_n(7u8, 128));
assert_eq!(der_octet_string(&long), Some(&long[3..]));
let mut longer = vec![0x04, 0x82, 0x01, 0x00];
longer.extend(std::iter::repeat_n(7u8, 256));
assert_eq!(der_octet_string(&longer), Some(&longer[4..]));
assert_eq!(der_octet_string(&[0x03, 0x01, 0xaa]), None);
assert_eq!(der_octet_string(&[0x04]), None);
assert_eq!(der_octet_string(&[0x04, 0x03, 0xaa, 0xbb]), None);
assert_eq!(der_octet_string(&[0x04, 0x01, 0xaa, 0xbb]), None);
assert_eq!(der_octet_string(&[0x04, 0x81, 0x01, 0xaa]), None);
assert_eq!(der_octet_string(&[0x04, 0x80]), None);
assert_eq!(
der_octet_string(&[0x04, 0x83, 0x00, 0x00, 0x01, 0xaa]),
None
);
assert_eq!(der_octet_string(&[]), None);
}
#[test]
fn ec_points_are_read_wrapped_or_bare() {
let ed25519 = [7u8; 32];
let mut wrapped = vec![0x04, 0x20];
wrapped.extend_from_slice(&ed25519);
assert_eq!(
ec_point_bytes(&wrapped, KeyKind::Ed25519),
Some(&ed25519[..])
);
assert_eq!(
ec_point_bytes(&ed25519, KeyKind::Ed25519),
Some(&ed25519[..])
);
let mut tricky = vec![0x04, 0x1e];
tricky.extend_from_slice(&[9u8; 30]);
assert_eq!(ec_point_bytes(&tricky, KeyKind::Ed25519), Some(&tricky[..]));
let mut sec1 = vec![0x04];
sec1.extend_from_slice(&[1u8; 64]);
let mut wrapped = vec![0x04, 0x41];
wrapped.extend_from_slice(&sec1);
for kind in [KeyKind::P256, KeyKind::Secp256k1] {
assert_eq!(ec_point_bytes(&wrapped, kind), Some(&sec1[..]));
assert_eq!(ec_point_bytes(&sec1, kind), Some(&sec1[..]));
assert_eq!(ec_point_bytes(&[0x04, 0x01, 0xaa], kind), Some(&[0xaa][..]));
}
let mut compressed = vec![0x02];
compressed.extend_from_slice(&[1u8; 64]);
assert_eq!(ec_point_bytes(&compressed, KeyKind::P256), None);
assert_eq!(ec_point_bytes(&[0x05, 0x01, 0xaa], KeyKind::P256), None);
assert_eq!(ec_point_bytes(&ed25519, KeyKind::Rsa), None);
}
#[test]
fn staging_labels_are_distinct() {
let a = staging_label();
let b = staging_label();
assert!(a.starts_with(STAGING_LABEL_PREFIX) && b.starts_with(STAGING_LABEL_PREFIX));
assert_ne!(a, b);
assert!(a.len() <= 64, "fits the usual label limits: {a}");
}
#[test]
fn the_digest_info_prefix_is_the_one_of_rfc_8017() {
let mut digest_info = SHA256_DIGEST_INFO_PREFIX.to_vec();
digest_info.extend_from_slice(&[0u8; 32]);
assert_eq!(digest_info.len(), 51);
assert_eq!(digest_info[1], 0x31, "SEQUENCE length covers the rest");
assert_eq!(
&digest_info[4..13],
&[0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04]
);
assert_eq!(&digest_info[13..15], &[0x02, 0x01], "id-sha256");
assert_eq!(
&digest_info[17..19],
&[0x04, 0x20],
"OCTET STRING of 32 bytes"
);
}
#[test]
fn tokens_convert_and_display() {
assert_eq!(Pkcs11Token::from("a"), Pkcs11Token::Label("a".to_owned()));
assert_eq!(
Pkcs11Token::from("a".to_owned()),
Pkcs11Token::Label("a".to_owned())
);
assert_eq!(Pkcs11Token::from(7), Pkcs11Token::Slot(7));
assert_eq!(Pkcs11Token::from("a").to_string(), "token `a`");
assert_eq!(Pkcs11Token::from(7).to_string(), "slot 7");
}
#[test]
fn options_default_and_build() {
let options = Pkcs11KeyStoreOptions::new();
assert_eq!(options, Pkcs11KeyStoreOptions::default());
assert_eq!(options.sessions, 4);
assert_eq!(options.rsa_algorithm, JwsAlgorithm::RS256);
assert_eq!(options.algorithms, None);
let options = options
.sessions(1)
.rsa_algorithm(JwsAlgorithm::PS256)
.algorithms([JwsAlgorithm::EdDSA]);
assert_eq!(options.sessions, 1);
assert_eq!(options.rsa_algorithm, JwsAlgorithm::PS256);
assert_eq!(options.algorithms, Some(vec![JwsAlgorithm::EdDSA]));
}
#[test]
fn bad_options_are_refused_before_the_module_loads() {
let no_sessions = Pkcs11KeyStore::open_with(
"/nonexistent/module.so",
"token",
None,
Pkcs11KeyStoreOptions::new().sessions(0),
)
.unwrap_err();
assert_eq!(
no_sessions.to_string(),
"key store backend error: PKCS#11 key store options: `sessions` must be at least 1"
);
let wrong_rsa = Pkcs11KeyStore::open_with(
"/nonexistent/module.so",
"token",
None,
Pkcs11KeyStoreOptions::new().rsa_algorithm(JwsAlgorithm::ES256),
)
.unwrap_err();
assert_eq!(
wrong_rsa.to_string(),
"key store backend error: PKCS#11 key store options: `rsa_algorithm` must be RS256 or PS256, not ES256"
);
}
#[test]
fn a_missing_module_is_reported_with_its_path() {
let error = Pkcs11KeyStore::open("/nonexistent/module.so", "token", None).unwrap_err();
let KeyStoreError::Backend(source) = &error else {
panic!("expected Backend, got {error:?}");
};
let pkcs11 = source.downcast_ref::<Pkcs11Error>().expect("a Pkcs11Error");
assert!(
matches!(pkcs11, Pkcs11Error::Module { module, .. } if module == Path::new("/nonexistent/module.so"))
);
assert!(
error
.to_string()
.starts_with("key store backend error: PKCS#11 module `/nonexistent/module.so`: "),
"{error}"
);
assert!(pkcs11.source().is_some());
}
#[test]
fn errors_that_close_a_session_are_known() {
let call = |rv| Pkcs11Error::Call {
function: "C_Sign",
source: CryptokiError::Pkcs11(rv, cryptoki::context::Function::Sign),
};
assert!(call(RvError::SessionHandleInvalid).closes_session());
assert!(call(RvError::DeviceRemoved).closes_session());
assert!(!call(RvError::KeyHandleInvalid).closes_session());
assert!(!Pkcs11Error::TokenNotFound(Pkcs11Token::Slot(1)).closes_session());
assert_eq!(
Pkcs11Error::SignatureLength {
alg: JwsAlgorithm::ES256,
expected: 64,
actual: 70
}
.to_string(),
"PKCS#11 module returned a 70-byte signature for ES256, expected 64"
);
}
}