use crate::algorithm::{
CipherAlgorithm, HashAlgorithm, KeyTransportAlgorithm, KeyWrapAlgorithm, SignatureAlgorithm,
};
use crate::backend::Operation;
use crate::error::{Error, Result};
use crate::traits::{Decryptor, Encryptor, KeyAgreement, KeyWrapper, Signer, Verifier};
use cryptoki::mechanism::elliptic_curve::{EcKdf, Ecdh1DeriveParams};
use cryptoki::mechanism::rsa::{PkcsMgfType, PkcsOaepParams, PkcsOaepSource, PkcsPssParams};
use cryptoki::mechanism::{Mechanism, MechanismType};
use cryptoki::object::{Attribute, AttributeType, KeyType, ObjectClass, ObjectHandle};
use cryptoki::slot::Slot;
use cryptoki::types::Ulong;
use zeroize::{Zeroize, Zeroizing};
use std::borrow::Cow;
use std::collections::HashMap;
use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex, OnceLock, Weak};
pub struct Pkcs11Provider {
pkcs11: cryptoki::context::Pkcs11,
slot: cryptoki::slot::Slot,
module: ModuleIdentity,
}
impl Pkcs11Provider {
pub fn new(library_path: &Path) -> Result<Self> {
let pkcs11 = load_initialized_context(library_path)?;
let slots = pkcs11
.get_slots_with_initialized_token()
.map_err(|e| Error::Pkcs11(format!("C_GetSlotList failed: {e}")))?;
let slot = select_single_initialized_slot(&slots)?;
Ok(Self {
pkcs11,
slot,
module: module_identity(library_path),
})
}
pub fn new_with_slot_id(library_path: &Path, slot_id: u64) -> Result<Self> {
let pkcs11 = load_initialized_context(library_path)?;
let slot = Slot::try_from(slot_id)
.map_err(|e| Error::Pkcs11(format!("invalid PKCS#11 slot id {slot_id}: {e}")))?;
let token_info = pkcs11
.get_token_info(slot)
.map_err(|e| Error::Pkcs11(format!("C_GetTokenInfo failed for slot {slot}: {e}")))?;
if !token_info.token_initialized() {
return Err(Error::Pkcs11(format!(
"slot {slot} does not contain an initialized token"
)));
}
Ok(Self {
pkcs11,
slot,
module: module_identity(library_path),
})
}
pub fn new_with_token(
library_path: &Path,
token_label: &str,
token_serial: Option<&str>,
) -> Result<Self> {
let pkcs11 = load_initialized_context(library_path)?;
let slots = pkcs11
.get_slots_with_initialized_token()
.map_err(|e| Error::Pkcs11(format!("C_GetSlotList failed: {e}")))?;
let mut matches = Vec::new();
for slot in slots {
let token_info = pkcs11.get_token_info(slot).map_err(|e| {
Error::Pkcs11(format!("C_GetTokenInfo failed for slot {slot}: {e}"))
})?;
if token_info.label() == token_label
&& token_serial.is_none_or(|serial| token_info.serial_number() == serial)
{
matches.push(slot);
}
}
let slot = select_unique_matching_token(&matches, token_label, token_serial)?;
Ok(Self {
pkcs11,
slot,
module: module_identity(library_path),
})
}
pub fn slot_id(&self) -> u64 {
self.slot.id()
}
fn open_session_on_slot(&self, pin: &[u8], slot: Slot) -> Result<Pkcs11Session> {
use cryptoki::error::{Error as CrError, RvError};
let token = self.token_identity(slot)?;
let raw_pin = cryptoki::types::RawAuthPin::new(Box::new(pin.to_vec()));
let mut logins = login_registry()
.lock()
.map_err(|e| Error::Pkcs11(format!("PKCS#11 login registry lock poisoned: {e}")))?;
let verifier = logins.verifier(pin)?;
let record = logins.track(token);
let session = self
.pkcs11
.open_rw_session(slot)
.map_err(|e| Error::Pkcs11(format!("C_OpenSession failed: {e}")))?;
match session.login_with_raw(cryptoki::session::UserType::User, &raw_pin) {
Ok(()) => record.logged_in(verifier),
Err(CrError::Pkcs11(RvError::UserAlreadyLoggedIn, _)) => record.check(&verifier)?,
Err(e) => return Err(Error::Pkcs11(format!("C_Login failed: {e}"))),
}
let session = Arc::new(Mutex::new(session));
record.sessions.push(Arc::downgrade(&session));
drop(logins);
Ok(Pkcs11Session {
session,
pkcs11: self.pkcs11.clone(),
slot,
})
}
fn token_identity(&self, slot: Slot) -> Result<TokenIdentity> {
let token_info = self
.pkcs11
.get_token_info(slot)
.map_err(|e| Error::Pkcs11(format!("C_GetTokenInfo failed for slot {slot}: {e}")))?;
Ok(TokenIdentity {
module: self.module.clone(),
slot: slot.id(),
serial: token_info.serial_number().to_owned(),
label: token_info.label().to_owned(),
})
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
enum ModuleIdentity {
#[cfg(unix)]
File { dev: u64, ino: u64 },
Path(PathBuf),
}
fn module_identity(library_path: &Path) -> ModuleIdentity {
#[cfg(unix)]
if let Ok(metadata) = std::fs::metadata(library_path) {
use std::os::unix::fs::MetadataExt;
return ModuleIdentity::File {
dev: metadata.dev(),
ino: metadata.ino(),
};
}
ModuleIdentity::Path(
std::fs::canonicalize(library_path).unwrap_or_else(|_| library_path.to_path_buf()),
)
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
struct TokenIdentity {
module: ModuleIdentity,
slot: u64,
serial: String,
label: String,
}
const PIN_VERIFIER_KEY_LEN: usize = 32;
const MAX_PIN_MISMATCHES: u32 = 3;
#[derive(Default)]
struct LoginRegistry {
key: Option<Zeroizing<Vec<u8>>>,
tokens: HashMap<TokenIdentity, LoginRecord>,
}
#[derive(Default)]
struct LoginRecord {
verifier: Option<Zeroizing<Vec<u8>>>,
sessions: Vec<Weak<Mutex<cryptoki::session::Session>>>,
mismatches: u32,
}
fn login_registry() -> &'static Mutex<LoginRegistry> {
static LOGINS: OnceLock<Mutex<LoginRegistry>> = OnceLock::new();
LOGINS.get_or_init(Mutex::default)
}
impl LoginRegistry {
fn verifier(&mut self, pin: &[u8]) -> Result<Zeroizing<Vec<u8>>> {
let key = match &mut self.key {
Some(key) => key.as_slice(),
empty => empty
.insert(Zeroizing::new(crate::backend::random_bytes(
PIN_VERIFIER_KEY_LEN,
)?))
.as_slice(),
};
crate::digest::compute_hmac(HashAlgorithm::Sha256, key, pin).map(Zeroizing::new)
}
fn track(&mut self, token: TokenIdentity) -> &mut LoginRecord {
let record = self.tokens.entry(token).or_default();
record.sessions.retain(|session| session.strong_count() > 0);
if record.sessions.is_empty() {
record.verifier = None;
record.mismatches = 0;
}
record
}
}
impl LoginRecord {
fn logged_in(&mut self, verifier: Zeroizing<Vec<u8>>) {
self.verifier = Some(verifier);
self.mismatches = 0;
}
fn check(&mut self, verifier: &[u8]) -> Result<()> {
if self.mismatches >= MAX_PIN_MISMATCHES {
return Err(Error::Pkcs11(format!(
"{MAX_PIN_MISMATCHES} wrong PINs while the token is logged in: further sessions \
are refused until riptering's sessions on the token are closed"
)));
}
match &self.verifier {
Some(recorded) if crate::digest::constant_time_eq(recorded, verifier) => {
self.mismatches = 0;
Ok(())
}
Some(_) => {
self.mismatches += 1;
if self.mismatches >= MAX_PIN_MISMATCHES {
self.verifier = None;
}
Err(Error::Pkcs11(
"token is already logged in by this process with a different PIN".into(),
))
}
None => Err(Error::Pkcs11(
"cannot verify PIN: token already logged in outside riptering".into(),
)),
}
}
}
fn load_initialized_context(library_path: &Path) -> Result<cryptoki::context::Pkcs11> {
use cryptoki::context::{CInitializeArgs, CInitializeFlags};
use cryptoki::error::{Error as CrError, RvError};
let pkcs11 = cryptoki::context::Pkcs11::new(library_path)
.map_err(|e| Error::Pkcs11(format!("failed to load PKCS#11 library: {e}")))?;
match pkcs11.initialize(CInitializeArgs::new(CInitializeFlags::OS_LOCKING_OK)) {
Ok(()) => {}
Err(CrError::Pkcs11(RvError::CryptokiAlreadyInitialized, _)) => {}
Err(e) => return Err(Error::Pkcs11(format!("C_Initialize failed: {e}"))),
}
Ok(pkcs11)
}
fn select_single_initialized_slot(slots: &[Slot]) -> Result<Slot> {
match slots {
[] => Err(Error::Pkcs11(
"no slots with initialized token found".into(),
)),
[slot] => Ok(*slot),
_ => Err(Error::Pkcs11(format!(
"multiple initialized token slots found ({}); use Pkcs11Provider::new_with_slot_id \
or Pkcs11Provider::new_with_token to pin the intended token",
format_slot_list(slots)
))),
}
}
fn select_unique_matching_token(
slots: &[Slot],
token_label: &str,
token_serial: Option<&str>,
) -> Result<Slot> {
match slots {
[] => Err(Error::Pkcs11(format!(
"no initialized token matches label {token_label:?}{}",
token_serial
.map(|serial| format!(" and serial {serial:?}"))
.unwrap_or_default()
))),
[slot] => Ok(*slot),
_ => Err(Error::Pkcs11(format!(
"multiple initialized tokens match label {token_label:?}{} ({}); add a serial \
number or select by slot id",
token_serial
.map(|serial| format!(" and serial {serial:?}"))
.unwrap_or_default(),
format_slot_list(slots)
))),
}
}
fn format_slot_list(slots: &[Slot]) -> String {
slots
.iter()
.map(|slot| slot.id().to_string())
.collect::<Vec<_>>()
.join(", ")
}
impl Pkcs11Provider {
pub fn open_session(&self, pin: &str) -> Result<Pkcs11Session> {
self.open_session_bytes(pin.as_bytes())
}
pub fn open_session_bytes(&self, pin: &[u8]) -> Result<Pkcs11Session> {
self.open_session_on_slot(pin, self.slot)
}
}
pub struct Pkcs11Session {
session: Arc<Mutex<cryptoki::session::Session>>,
pkcs11: cryptoki::context::Pkcs11,
slot: Slot,
}
impl Pkcs11Session {
pub fn find_private_key(&self, label: &str) -> Result<ObjectHandle> {
self.find_object(label, ObjectClass::PRIVATE_KEY, None)
}
pub fn find_private_key_by_id(&self, label: &str, id: &[u8]) -> Result<ObjectHandle> {
self.find_object(label, ObjectClass::PRIVATE_KEY, Some(id))
}
pub fn find_public_key(&self, label: &str) -> Result<ObjectHandle> {
self.find_object(label, ObjectClass::PUBLIC_KEY, None)
}
pub fn find_public_key_by_id(&self, label: &str, id: &[u8]) -> Result<ObjectHandle> {
self.find_object(label, ObjectClass::PUBLIC_KEY, Some(id))
}
pub fn find_secret_key(&self, label: &str) -> Result<ObjectHandle> {
self.find_object(label, ObjectClass::SECRET_KEY, None)
}
pub fn find_secret_key_by_id(&self, label: &str, id: &[u8]) -> Result<ObjectHandle> {
self.find_object(label, ObjectClass::SECRET_KEY, Some(id))
}
pub fn session(&self) -> &Arc<Mutex<cryptoki::session::Session>> {
&self.session
}
fn validate_key(&self, key: ObjectHandle, expected: TokenKey) -> Result<()> {
let session = self
.session
.lock()
.map_err(|e| Error::Pkcs11(format!("session lock poisoned: {e}")))?;
let attrs = session
.get_attributes(key, expected.attributes())
.map_err(|e| Error::Pkcs11(format!("required key attributes unavailable: {e}")))?;
validate_token_key_attributes(&attrs, expected)
}
fn find_object(
&self,
label: &str,
class: ObjectClass,
id: Option<&[u8]>,
) -> Result<ObjectHandle> {
let mut template = vec![
Attribute::Class(class),
Attribute::Label(label.as_bytes().to_vec()),
];
if let Some(id) = id {
template.push(Attribute::Id(id.to_vec()));
}
let session = self
.session
.lock()
.map_err(|e| Error::Pkcs11(format!("session lock poisoned: {e}")))?;
let mut objects = session
.find_objects(&template)
.map_err(|e| Error::Pkcs11(format!("C_FindObjects failed: {e}")))?;
match objects.len() {
0 => Err(Error::Pkcs11(format!(
"no {class} object found with label {label:?}{}",
id.map(|_| " and matching CKA_ID").unwrap_or_default()
))),
1 => Ok(objects.remove(0)),
n => Err(Error::Pkcs11(format!(
"ambiguous {class} object lookup: {n} objects found with label {label:?}{}; \
use a unique label or the *_by_id lookup methods",
id.map(|_| " and matching CKA_ID").unwrap_or_default()
))),
}
}
}
#[derive(Clone, Copy, Debug)]
enum TokenKey {
Rsa,
Ec(crate::algorithm::EcCurve),
Ed25519,
Aes(crate::algorithm::AesKeySize),
HmacSha256,
}
impl TokenKey {
fn attributes(self) -> &'static [AttributeType] {
match self {
Self::Rsa => &[AttributeType::KeyType, AttributeType::Modulus],
Self::Ec(_) | Self::Ed25519 => &[AttributeType::KeyType, AttributeType::EcParams],
Self::Aes(_) => &[AttributeType::KeyType, AttributeType::ValueLen],
Self::HmacSha256 => &[AttributeType::KeyType],
}
}
}
fn required_attribute(attrs: &[Attribute], kind: AttributeType) -> Result<&Attribute> {
let mut matches = attrs.iter().filter(|attr| attr.attribute_type() == kind);
let value = matches
.next()
.ok_or_else(|| Error::Pkcs11(format!("required key attribute {kind:?} is unavailable")))?;
if matches.next().is_some() {
return Err(Error::Pkcs11(format!("duplicate key attribute {kind:?}")));
}
Ok(value)
}
fn validate_token_key_attributes(attrs: &[Attribute], expected: TokenKey) -> Result<()> {
let Attribute::KeyType(actual_type) = required_attribute(attrs, AttributeType::KeyType)? else {
unreachable!("attribute type matches its variant")
};
let expected_type = match expected {
TokenKey::Rsa => KeyType::RSA,
TokenKey::Ec(_) => KeyType::EC,
TokenKey::Ed25519 => KeyType::EC_EDWARDS,
TokenKey::Aes(_) => KeyType::AES,
TokenKey::HmacSha256 => KeyType::GENERIC_SECRET,
};
if *actual_type != expected_type {
return Err(Error::Pkcs11(format!(
"token key type does not match {expected:?}"
)));
}
match expected {
TokenKey::Rsa => {
let Attribute::Modulus(modulus) = required_attribute(attrs, AttributeType::Modulus)?
else {
unreachable!("attribute type matches its variant")
};
let modulus = &modulus[modulus
.iter()
.position(|byte| *byte != 0)
.unwrap_or(modulus.len())..];
let bits = modulus.first().map_or(0, |first| {
(modulus.len() - 1) * 8 + (8 - first.leading_zeros() as usize)
});
if bits < 2048
|| modulus.last().is_none_or(|byte| byte & 1 == 0)
|| (cfg!(feature = "fips") && !bits.is_multiple_of(2))
{
return Err(Error::Pkcs11("token RSA modulus must be odd and at least 2048 bits; FIPS additionally requires an even bit length".into()));
}
}
TokenKey::Ec(curve) => {
let Attribute::EcParams(params) = required_attribute(attrs, AttributeType::EcParams)?
else {
unreachable!("attribute type matches its variant")
};
if ec_curve_for_ec_params(params) != Some(curve) {
return Err(Error::Pkcs11(format!(
"token EC parameters do not match {curve:?}"
)));
}
}
TokenKey::Ed25519 => {
let Attribute::EcParams(params) = required_attribute(attrs, AttributeType::EcParams)?
else {
unreachable!("attribute type matches its variant")
};
if params != &[0x06, 0x03, 0x2b, 0x65, 0x70] && params != b"\x13\x0cedwards25519" {
return Err(Error::Pkcs11(
"token Edwards parameters must identify Ed25519".into(),
));
}
}
TokenKey::Aes(size) => check_kek_value_len(attrs, size.key_len())?,
TokenKey::HmacSha256 => {}
}
Ok(())
}
fn asymmetric_signature_key(
algorithm: SignatureAlgorithm,
operation: Operation,
) -> Result<TokenKey> {
signature_mechanism(&algorithm, operation)?;
match algorithm {
SignatureAlgorithm::RsaPkcs1v15(_) | SignatureAlgorithm::RsaPss(_) => Ok(TokenKey::Rsa),
SignatureAlgorithm::Ecdsa(curve, _) => Ok(TokenKey::Ec(curve)),
SignatureAlgorithm::Ed25519 => Ok(TokenKey::Ed25519),
_ => Err(Error::Key(
"asymmetric token signature key required; use Pkcs11HmacSigner for HMAC".into(),
)),
}
}
#[allow(unreachable_patterns)] fn signature_mechanism(
algo: &SignatureAlgorithm,
operation: Operation,
) -> Result<Mechanism<'static>> {
match algo {
SignatureAlgorithm::RsaPkcs1v15(hash) => match hash {
HashAlgorithm::Sha1 => Ok(Mechanism::Sha1RsaPkcs),
HashAlgorithm::Sha256 => Ok(Mechanism::Sha256RsaPkcs),
HashAlgorithm::Sha384 => Ok(Mechanism::Sha384RsaPkcs),
HashAlgorithm::Sha512 => Ok(Mechanism::Sha512RsaPkcs),
other => Err(Error::unsupported(
operation,
format!("RSA PKCS#1 v1.5 with {other:?} not supported via PKCS#11"),
)),
},
SignatureAlgorithm::RsaPss(hash) => {
let (hash_mech, mgf, s_len) = pss_params_for(*hash, operation)?;
let pss = PkcsPssParams {
hash_alg: hash_mech,
mgf,
s_len,
};
match hash {
HashAlgorithm::Sha1 => Ok(Mechanism::Sha1RsaPkcsPss(pss)),
HashAlgorithm::Sha256 => Ok(Mechanism::Sha256RsaPkcsPss(pss)),
HashAlgorithm::Sha384 => Ok(Mechanism::Sha384RsaPkcsPss(pss)),
HashAlgorithm::Sha512 => Ok(Mechanism::Sha512RsaPkcsPss(pss)),
other => Err(Error::unsupported(
operation,
format!("RSA-PSS with {other:?} not supported via PKCS#11"),
)),
}
}
SignatureAlgorithm::Ecdsa(_, _) => Ok(Mechanism::Ecdsa),
SignatureAlgorithm::Ed25519 => {
use cryptoki::mechanism::eddsa::{EddsaParams, EddsaSignatureScheme};
Ok(Mechanism::Eddsa(EddsaParams::new(
EddsaSignatureScheme::Ed25519,
)))
}
SignatureAlgorithm::Hmac(hash) => match hash {
HashAlgorithm::Sha256 => Ok(Mechanism::Sha256Hmac),
other => Err(Error::unsupported(
operation,
format!(
"HMAC with {other:?} not supported via PKCS#11 (only SHA-256 \
HMAC is currently wired up; cryptoki 0.12 exposes more)"
),
)),
},
other => Err(Error::unsupported(
operation,
format!("{other:?} not supported via PKCS#11"),
)),
}
}
fn pss_params_for(
hash: HashAlgorithm,
operation: Operation,
) -> Result<(MechanismType, PkcsMgfType, Ulong)> {
match hash {
HashAlgorithm::Sha1 => Ok((MechanismType::SHA1, PkcsMgfType::MGF1_SHA1, 20.into())),
HashAlgorithm::Sha256 => Ok((MechanismType::SHA256, PkcsMgfType::MGF1_SHA256, 32.into())),
HashAlgorithm::Sha384 => Ok((MechanismType::SHA384, PkcsMgfType::MGF1_SHA384, 48.into())),
HashAlgorithm::Sha512 => Ok((MechanismType::SHA512, PkcsMgfType::MGF1_SHA512, 64.into())),
other => Err(Error::unsupported(
operation,
format!("RSA-PSS with {other:?}"),
)),
}
}
fn oaep_mechanism<'a>(
cfg: &crate::algorithm::OaepConfig,
label: Option<&'a [u8]>,
operation: Operation,
) -> Result<Mechanism<'a>> {
let hash_mech = hash_to_mechanism_type(cfg.digest, operation)?;
let mgf = hash_to_mgf(cfg.mgf_digest, operation)?;
let source = match label {
Some(bytes) => PkcsOaepSource::data_specified(bytes),
None => PkcsOaepSource::empty(),
};
let params = PkcsOaepParams::new(hash_mech, mgf, source);
Ok(Mechanism::RsaPkcsOaep(params))
}
fn hash_to_mechanism_type(h: HashAlgorithm, operation: Operation) -> Result<MechanismType> {
match h {
HashAlgorithm::Sha1 => Ok(MechanismType::SHA1),
HashAlgorithm::Sha256 => Ok(MechanismType::SHA256),
HashAlgorithm::Sha384 => Ok(MechanismType::SHA384),
HashAlgorithm::Sha512 => Ok(MechanismType::SHA512),
other => Err(Error::unsupported(
operation,
format!("hash {other:?} not supported for PKCS#11 OAEP"),
)),
}
}
fn hash_to_mgf(h: HashAlgorithm, operation: Operation) -> Result<PkcsMgfType> {
match h {
HashAlgorithm::Sha1 => Ok(PkcsMgfType::MGF1_SHA1),
HashAlgorithm::Sha256 => Ok(PkcsMgfType::MGF1_SHA256),
HashAlgorithm::Sha384 => Ok(PkcsMgfType::MGF1_SHA384),
HashAlgorithm::Sha512 => Ok(PkcsMgfType::MGF1_SHA512),
other => Err(Error::unsupported(
operation,
format!("MGF with {other:?} not supported"),
)),
}
}
fn prepare_sign_data<'a>(algo: &SignatureAlgorithm, data: &'a [u8]) -> Result<Cow<'a, [u8]>> {
match algo {
SignatureAlgorithm::Ecdsa(_, hash) => crate::digest::digest(*hash, data).map(Cow::Owned),
_ => Ok(Cow::Borrowed(data)),
}
}
pub struct Pkcs11Signer {
session: Arc<Mutex<cryptoki::session::Session>>,
key_handle: ObjectHandle,
algorithm: SignatureAlgorithm,
}
impl Pkcs11Signer {
pub fn new(
session: &Pkcs11Session,
key_label: &str,
algorithm: SignatureAlgorithm,
) -> Result<Self> {
let expected = asymmetric_signature_key(algorithm, Operation::Sign(algorithm))?;
let key_handle = session.find_private_key(key_label)?;
session.validate_key(key_handle, expected)?;
Ok(Self {
session: Arc::clone(&session.session),
key_handle,
algorithm,
})
}
}
impl Signer for Pkcs11Signer {
fn algorithm(&self) -> SignatureAlgorithm {
self.algorithm
}
fn sign(&self, data: &[u8]) -> Result<Vec<u8>> {
crate::backend::require_fips_approved(Operation::Sign(self.algorithm))?;
let mechanism = signature_mechanism(&self.algorithm, Operation::Sign(self.algorithm))?;
let sign_data = prepare_sign_data(&self.algorithm, data)?;
let session = self
.session
.lock()
.map_err(|e| Error::Pkcs11(format!("session lock poisoned: {e}")))?;
session
.sign(&mechanism, self.key_handle, &sign_data)
.map_err(|e| Error::Pkcs11(format!("C_Sign failed: {e}")))
}
}
pub struct Pkcs11Verifier {
session: Arc<Mutex<cryptoki::session::Session>>,
key_handle: ObjectHandle,
algorithm: SignatureAlgorithm,
}
impl Pkcs11Verifier {
pub fn new(
session: &Pkcs11Session,
key_label: &str,
algorithm: SignatureAlgorithm,
) -> Result<Self> {
let expected = asymmetric_signature_key(algorithm, Operation::Verify(algorithm))?;
let key_handle = session.find_public_key(key_label)?;
session.validate_key(key_handle, expected)?;
Ok(Self {
session: Arc::clone(&session.session),
key_handle,
algorithm,
})
}
}
impl Verifier for Pkcs11Verifier {
fn algorithm(&self) -> SignatureAlgorithm {
self.algorithm
}
fn verify(&self, data: &[u8], signature: &[u8]) -> Result<bool> {
crate::backend::require_fips_approved(Operation::Verify(self.algorithm))?;
let mechanism = signature_mechanism(&self.algorithm, Operation::Verify(self.algorithm))?;
let verify_data = prepare_sign_data(&self.algorithm, data)?;
let session = self
.session
.lock()
.map_err(|e| Error::Pkcs11(format!("session lock poisoned: {e}")))?;
match session.verify(&mechanism, self.key_handle, &verify_data, signature) {
Ok(()) => Ok(true),
Err(cryptoki::error::Error::Pkcs11(cryptoki::error::RvError::SignatureInvalid, _)) => {
Ok(false)
}
Err(cryptoki::error::Error::Pkcs11(cryptoki::error::RvError::SignatureLenRange, _)) => {
Ok(false)
}
Err(e) => Err(Error::Pkcs11(format!("C_Verify failed: {e}"))),
}
}
}
pub struct Pkcs11HmacSigner {
session: Arc<Mutex<cryptoki::session::Session>>,
key_handle: ObjectHandle,
algorithm: SignatureAlgorithm,
}
impl Pkcs11HmacSigner {
pub fn new(
session: &Pkcs11Session,
key_label: &str,
algorithm: SignatureAlgorithm,
) -> Result<Self> {
signature_mechanism(&algorithm, Operation::Sign(algorithm))?;
if !matches!(algorithm, SignatureAlgorithm::Hmac(HashAlgorithm::Sha256)) {
return Err(Error::Key("HMAC-SHA256 token key required".into()));
}
let key_handle = session.find_secret_key(key_label)?;
session.validate_key(key_handle, TokenKey::HmacSha256)?;
Ok(Self {
session: Arc::clone(&session.session),
key_handle,
algorithm,
})
}
}
impl Signer for Pkcs11HmacSigner {
fn algorithm(&self) -> SignatureAlgorithm {
self.algorithm
}
fn sign(&self, data: &[u8]) -> Result<Vec<u8>> {
crate::backend::require_fips_approved(Operation::Sign(self.algorithm))?;
let mechanism = signature_mechanism(&self.algorithm, Operation::Sign(self.algorithm))?;
let session = self
.session
.lock()
.map_err(|e| Error::Pkcs11(format!("session lock poisoned: {e}")))?;
session
.sign(&mechanism, self.key_handle, data)
.map_err(|e| Error::Pkcs11(format!("C_Sign (HMAC) failed: {e}")))
}
}
impl Verifier for Pkcs11HmacSigner {
fn algorithm(&self) -> SignatureAlgorithm {
self.algorithm
}
fn verify(&self, data: &[u8], signature: &[u8]) -> Result<bool> {
crate::backend::require_fips_approved(Operation::Verify(self.algorithm))?;
let mechanism = signature_mechanism(&self.algorithm, Operation::Verify(self.algorithm))?;
let session = self
.session
.lock()
.map_err(|e| Error::Pkcs11(format!("session lock poisoned: {e}")))?;
match session.verify(&mechanism, self.key_handle, data, signature) {
Ok(()) => Ok(true),
Err(cryptoki::error::Error::Pkcs11(cryptoki::error::RvError::SignatureInvalid, _)) => {
Ok(false)
}
Err(cryptoki::error::Error::Pkcs11(cryptoki::error::RvError::SignatureLenRange, _)) => {
Ok(false)
}
Err(e) => Err(Error::Pkcs11(format!("C_Verify (HMAC) failed: {e}"))),
}
}
}
pub struct Pkcs11Decryptor {
session: Arc<Mutex<cryptoki::session::Session>>,
key_handle: ObjectHandle,
algorithm: KeyTransportAlgorithm,
oaep_label: Option<Vec<u8>>,
}
impl Pkcs11Decryptor {
pub fn new(
session: &Pkcs11Session,
key_label: &str,
algorithm: KeyTransportAlgorithm,
) -> Result<Self> {
Self::new_with_oaep_label(session, key_label, algorithm, None)
}
pub fn new_with_oaep_label(
session: &Pkcs11Session,
key_label: &str,
algorithm: KeyTransportAlgorithm,
oaep_label: Option<Vec<u8>>,
) -> Result<Self> {
let key_handle = session.find_private_key(key_label)?;
session.validate_key(key_handle, TokenKey::Rsa)?;
Ok(Self {
session: Arc::clone(&session.session),
key_handle,
algorithm,
oaep_label,
})
}
}
impl Decryptor for Pkcs11Decryptor {
fn decrypt(&self, ciphertext: &[u8]) -> Result<Vec<u8>> {
crate::backend::require_fips_approved(Operation::TransportDecrypt(self.algorithm))?;
let mechanism = key_transport_mechanism(
&self.algorithm,
self.oaep_label.as_deref(),
Operation::TransportDecrypt(self.algorithm),
)?;
let session = self
.session
.lock()
.map_err(|e| Error::Pkcs11(format!("session lock poisoned: {e}")))?;
session
.decrypt(&mechanism, self.key_handle, ciphertext)
.map_err(|e| Error::Pkcs11(format!("C_Decrypt failed: {e}")))
}
}
pub struct Pkcs11Encryptor {
session: Arc<Mutex<cryptoki::session::Session>>,
key_handle: ObjectHandle,
algorithm: KeyTransportAlgorithm,
oaep_label: Option<Vec<u8>>,
}
impl Pkcs11Encryptor {
pub fn new(
session: &Pkcs11Session,
key_label: &str,
algorithm: KeyTransportAlgorithm,
) -> Result<Self> {
Self::new_with_oaep_label(session, key_label, algorithm, None)
}
pub fn new_with_oaep_label(
session: &Pkcs11Session,
key_label: &str,
algorithm: KeyTransportAlgorithm,
oaep_label: Option<Vec<u8>>,
) -> Result<Self> {
let key_handle = session.find_public_key(key_label)?;
session.validate_key(key_handle, TokenKey::Rsa)?;
Ok(Self {
session: Arc::clone(&session.session),
key_handle,
algorithm,
oaep_label,
})
}
}
impl Encryptor for Pkcs11Encryptor {
fn encrypt(&self, plaintext: &[u8]) -> Result<Vec<u8>> {
crate::backend::require_fips_approved(Operation::TransportEncrypt(self.algorithm))?;
let mechanism = key_transport_mechanism(
&self.algorithm,
self.oaep_label.as_deref(),
Operation::TransportEncrypt(self.algorithm),
)?;
let session = self
.session
.lock()
.map_err(|e| Error::Pkcs11(format!("session lock poisoned: {e}")))?;
session
.encrypt(&mechanism, self.key_handle, plaintext)
.map_err(|e| Error::Pkcs11(format!("C_Encrypt failed: {e}")))
}
}
fn key_transport_mechanism<'a>(
algo: &KeyTransportAlgorithm,
label: Option<&'a [u8]>,
operation: Operation,
) -> Result<Mechanism<'a>> {
match algo {
#[cfg(feature = "legacy")]
KeyTransportAlgorithm::RsaPkcs1v15 => Ok(Mechanism::RsaPkcs),
KeyTransportAlgorithm::RsaOaep(cfg) => oaep_mechanism(cfg, label, operation),
}
}
pub struct Pkcs11KeyWrapper {
session: Arc<Mutex<cryptoki::session::Session>>,
key_handle: ObjectHandle,
algorithm: KeyWrapAlgorithm,
wrap_call: KeyWrapCall,
unwrap_call: KeyWrapCall,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum KeyWrapCall {
KeyManagement,
Cipher,
Unsupported,
}
fn select_keywrap_call(key_management: bool, cipher: bool) -> KeyWrapCall {
if key_management {
KeyWrapCall::KeyManagement
} else if cipher {
KeyWrapCall::Cipher
} else {
KeyWrapCall::Unsupported
}
}
impl Pkcs11KeyWrapper {
pub fn new(
session: &Pkcs11Session,
key_label: &str,
algorithm: KeyWrapAlgorithm,
) -> Result<Self> {
let key_handle = session.find_secret_key(key_label)?;
#[allow(irrefutable_let_patterns)] if let KeyWrapAlgorithm::AesKw(size) = algorithm {
session.validate_key(key_handle, TokenKey::Aes(size))?;
}
let (wrap_call, unwrap_call) = keywrap_calls(session, algorithm)?;
Ok(Self {
session: Arc::clone(&session.session),
key_handle,
algorithm,
wrap_call,
unwrap_call,
})
}
}
fn keywrap_calls(
session: &Pkcs11Session,
algorithm: KeyWrapAlgorithm,
) -> Result<(KeyWrapCall, KeyWrapCall)> {
use cryptoki::error::{Error as CrError, RvError};
let Ok(mechanism) = keywrap_mechanism(&algorithm, Operation::Wrap(algorithm)) else {
return Ok((KeyWrapCall::Unsupported, KeyWrapCall::Unsupported));
};
match session
.pkcs11
.get_mechanism_info(session.slot, mechanism.mechanism_type())
{
Ok(info) => Ok(keywrap_calls_for(&info)),
Err(CrError::Pkcs11(RvError::MechanismInvalid, _)) => {
Ok((KeyWrapCall::Unsupported, KeyWrapCall::Unsupported))
}
Err(e) => Err(Error::Pkcs11(format!("C_GetMechanismInfo failed: {e}"))),
}
}
fn keywrap_calls_for(info: &cryptoki::mechanism::MechanismInfo) -> (KeyWrapCall, KeyWrapCall) {
(
select_keywrap_call(info.wrap(), info.encrypt()),
select_keywrap_call(info.unwrap(), info.decrypt()),
)
}
impl KeyWrapper for Pkcs11KeyWrapper {
fn wrap(&self, key_data: &[u8]) -> Result<Vec<u8>> {
let operation = Operation::Wrap(self.algorithm);
crate::backend::require_fips_approved(operation)?;
let mechanism = keywrap_mechanism(&self.algorithm, operation)?;
if key_data.len() < 16 || !key_data.len().is_multiple_of(8) {
return Err(Error::Crypto("invalid AES-KW input length".into()));
}
let session = self
.session
.lock()
.map_err(|e| Error::Pkcs11(format!("session lock poisoned: {e}")))?;
match self.wrap_call {
KeyWrapCall::KeyManagement => {
wrap_with_wrap_key(&session, &mechanism, self.key_handle, key_data)
}
KeyWrapCall::Cipher => session
.encrypt(&mechanism, self.key_handle, key_data)
.map_err(|e| Error::Pkcs11(format!("C_Encrypt (key wrap) failed: {e}"))),
KeyWrapCall::Unsupported => Err(Error::unsupported(
operation,
format!(
"the token offers {} to neither C_WrapKey nor C_Encrypt",
mechanism.mechanism_type()
),
)),
}
}
fn unwrap(&self, wrapped: &[u8]) -> Result<Vec<u8>> {
let operation = Operation::Unwrap(self.algorithm);
crate::backend::require_fips_approved(operation)?;
let mechanism = keywrap_mechanism(&self.algorithm, operation)?;
if wrapped.len() < 24 || !wrapped.len().is_multiple_of(8) {
return Err(Error::Crypto("invalid AES-KW input length".into()));
}
let session = self
.session
.lock()
.map_err(|e| Error::Pkcs11(format!("session lock poisoned: {e}")))?;
match self.unwrap_call {
KeyWrapCall::KeyManagement => {
unwrap_with_unwrap_key(&session, &mechanism, self.key_handle, wrapped)
}
KeyWrapCall::Cipher => session
.decrypt(&mechanism, self.key_handle, wrapped)
.map_err(|e| Error::Pkcs11(format!("C_Decrypt (key unwrap) failed: {e}"))),
KeyWrapCall::Unsupported => Err(Error::unsupported(
operation,
format!(
"the token offers {} to neither C_UnwrapKey nor C_Decrypt",
mechanism.mechanism_type()
),
)),
}
}
}
fn wrap_with_wrap_key(
session: &cryptoki::session::Session,
mechanism: &Mechanism,
kek: ObjectHandle,
key_data: &[u8],
) -> Result<Vec<u8>> {
let template = SecretAttributes(vec![
Attribute::Class(ObjectClass::SECRET_KEY),
Attribute::KeyType(KeyType::GENERIC_SECRET),
Attribute::Token(false),
Attribute::Extractable(true),
Attribute::Value(key_data.to_vec()),
]);
let created = session.create_object(&template.0);
drop(template);
let key =
created.map_err(|e| Error::Pkcs11(format!("C_CreateObject (key to wrap) failed: {e}")))?;
let wrapped = session
.wrap_key(mechanism, kek, key)
.map_err(|e| Error::Pkcs11(format!("C_WrapKey failed: {e}")));
session.destroy_object(key).map_err(|e| {
Error::Pkcs11(format!(
"C_DestroyObject failed for the temporary key-to-wrap object; close the session \
to purge it: {e}"
))
})?;
wrapped
}
fn unwrap_with_unwrap_key(
session: &cryptoki::session::Session,
mechanism: &Mechanism,
kek: ObjectHandle,
wrapped: &[u8],
) -> Result<Vec<u8>> {
let template = [
Attribute::Class(ObjectClass::SECRET_KEY),
Attribute::KeyType(KeyType::GENERIC_SECRET),
Attribute::Token(false),
Attribute::Sensitive(false),
Attribute::Extractable(true),
];
let key = session
.unwrap_key(mechanism, kek, wrapped, &template)
.map_err(|e| Error::Pkcs11(format!("C_UnwrapKey failed: {e}")))?;
read_and_destroy_temporary_secret(
|| read_secret_value(session, key, "unwrapped key"),
|| {
session.destroy_object(key).map_err(|e| {
Error::Pkcs11(format!(
"C_DestroyObject failed for the unwrapped key object; close the session to \
purge it: {e}"
))
})
},
)
}
fn read_secret_value(
session: &cryptoki::session::Session,
key: ObjectHandle,
description: &str,
) -> Result<Zeroizing<Vec<u8>>> {
let attrs = session
.get_attributes(key, &[AttributeType::Value])
.map_err(|e| Error::Pkcs11(format!("C_GetAttributeValue failed: {e}")))?;
extract_secret_value(SecretAttributes(attrs), description)
}
struct SecretAttributes(Vec<Attribute>);
impl Drop for SecretAttributes {
fn drop(&mut self) {
for attr in &mut self.0 {
if let Attribute::Value(value) = attr {
value.zeroize();
}
}
}
}
fn extract_secret_value(
mut attrs: SecretAttributes,
description: &str,
) -> Result<Zeroizing<Vec<u8>>> {
let mut indices = attrs
.0
.iter()
.enumerate()
.filter_map(|(index, attr)| matches!(attr, Attribute::Value(_)).then_some(index));
let index = indices
.next()
.ok_or_else(|| Error::Pkcs11(format!("CKA_VALUE not present on {description}")))?;
if indices.next().is_some() {
return Err(Error::Pkcs11(format!(
"duplicate CKA_VALUE on {description}"
)));
}
let Attribute::Value(value) = &mut attrs.0[index] else {
unreachable!()
};
Ok(Zeroizing::new(std::mem::take(value)))
}
fn read_and_destroy_temporary_secret(
read: impl FnOnce() -> Result<Zeroizing<Vec<u8>>>,
destroy: impl FnOnce() -> Result<()>,
) -> Result<Vec<u8>> {
let value = read();
destroy()?;
let mut value = value?;
Ok(std::mem::take(&mut *value))
}
fn check_kek_value_len(attrs: &[Attribute], expected: usize) -> Result<()> {
let Attribute::ValueLen(len) = required_attribute(attrs, AttributeType::ValueLen)? else {
unreachable!("attribute type matches its variant")
};
let actual = **len;
if usize::try_from(actual).ok() != Some(expected) {
return Err(Error::Pkcs11(format!(
"PKCS#11 AES length mismatch: token key has CKA_VALUE_LEN {actual} bytes, \
algorithm declares {expected} bytes"
)));
}
Ok(())
}
fn keywrap_mechanism(algo: &KeyWrapAlgorithm, _operation: Operation) -> Result<Mechanism<'static>> {
match algo {
KeyWrapAlgorithm::AesKw(_) => Ok(Mechanism::AesKeyWrap),
#[cfg(feature = "legacy")]
KeyWrapAlgorithm::TripleDesKw => Err(Error::unsupported(
_operation,
"3DES key wrap not supported via PKCS#11",
)),
}
}
pub struct Pkcs11KeyAgreement {
session: Arc<Mutex<cryptoki::session::Session>>,
key_handle: ObjectHandle,
key_len: usize,
curve: crate::algorithm::EcCurve,
}
impl Pkcs11KeyAgreement {
pub fn new(session: &Pkcs11Session, key_label: &str, key_len: usize) -> Result<Self> {
let key_handle = session.find_private_key(key_label)?;
let guard = session
.session
.lock()
.map_err(|e| Error::Pkcs11(format!("session lock poisoned: {e}")))?;
let attrs = guard
.get_attributes(
key_handle,
&[AttributeType::KeyType, AttributeType::EcParams],
)
.map_err(|e| Error::Pkcs11(format!("C_GetAttributeValue failed: {e}")))?;
drop(guard);
let curve = attrs.iter().find_map(|attr| match attr {
Attribute::EcParams(params) => ec_curve_for_ec_params(params),
_ => None,
});
let curve = curve.ok_or_else(|| {
Error::Pkcs11("ECDH requires readable P-256/P-384/P-521 parameters".into())
})?;
validate_token_key_attributes(&attrs, TokenKey::Ec(curve))?;
let expected_len = match curve {
crate::algorithm::EcCurve::P256 => 32,
crate::algorithm::EcCurve::P384 => 48,
crate::algorithm::EcCurve::P521 => 66,
};
if key_len != expected_len {
return Err(Error::Pkcs11(format!(
"ECDH shared secret length must be {expected_len} for {curve:?}"
)));
}
Ok(Self {
session: Arc::clone(&session.session),
key_handle,
key_len,
curve,
})
}
}
const OID_DER_P256: &[u8] = &[
0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, ];
const OID_DER_P384: &[u8] = &[0x06, 0x05, 0x2b, 0x81, 0x04, 0x00, 0x22]; const OID_DER_P521: &[u8] = &[0x06, 0x05, 0x2b, 0x81, 0x04, 0x00, 0x23];
fn ec_curve_for_ec_params(params: &[u8]) -> Option<crate::algorithm::EcCurve> {
match params {
OID_DER_P256 => Some(crate::algorithm::EcCurve::P256),
OID_DER_P384 => Some(crate::algorithm::EcCurve::P384),
OID_DER_P521 => Some(crate::algorithm::EcCurve::P521),
_ => None,
}
}
impl KeyAgreement for Pkcs11KeyAgreement {
fn agree(&self, peer_public_key: &[u8]) -> Result<Vec<u8>> {
crate::backend::require_fips_approved(Operation::Agreement(self.curve))?;
let ec_params = Ecdh1DeriveParams::new(EcKdf::null(), peer_public_key);
let mechanism = Mechanism::Ecdh1Derive(ec_params);
let template = vec![
Attribute::Class(ObjectClass::SECRET_KEY),
Attribute::KeyType(cryptoki::object::KeyType::GENERIC_SECRET),
Attribute::Encrypt(false),
Attribute::Decrypt(false),
Attribute::ValueLen(self.key_len.try_into().map_err(|_| {
Error::Pkcs11(format!("key_len {} too large for Ulong", self.key_len))
})?),
Attribute::Extractable(true),
Attribute::Sensitive(false),
Attribute::Token(false),
];
let session = self
.session
.lock()
.map_err(|e| Error::Pkcs11(format!("session lock poisoned: {e}")))?;
let derived_key = session
.derive_key(&mechanism, self.key_handle, &template)
.map_err(|e| Error::Pkcs11(format!("C_DeriveKey (ECDH) failed: {e}")))?;
read_and_destroy_temporary_secret(
|| read_secret_value(&session, derived_key, "derived ECDH key"),
|| {
session.destroy_object(derived_key).map_err(|e| {
Error::Pkcs11(format!(
"C_DestroyObject failed for derived ECDH key; close the session to purge it: {e}"
))
})
},
)
}
}
pub struct Pkcs11Cipher {
session: Arc<Mutex<cryptoki::session::Session>>,
key_handle: ObjectHandle,
algorithm: CipherAlgorithm,
}
impl Pkcs11Cipher {
pub fn new(
session: &Pkcs11Session,
key_label: &str,
algorithm: CipherAlgorithm,
) -> Result<Self> {
validate_pkcs11_cipher_algorithm(algorithm)?;
let key_handle = session.find_secret_key(key_label)?;
if let CipherAlgorithm::AesGcm(size) = algorithm {
session.validate_key(key_handle, TokenKey::Aes(size))?;
}
Ok(Self {
session: Arc::clone(&session.session),
key_handle,
algorithm,
})
}
pub fn encrypt(&self, plaintext: &[u8]) -> Result<Vec<u8>> {
crate::backend::require_fips_approved(Operation::Encrypt(self.algorithm))?;
let session = self
.session
.lock()
.map_err(|e| Error::Pkcs11(format!("session lock poisoned: {e}")))?;
match self.algorithm {
CipherAlgorithm::AesCbc(_) => Err(unsupported_pkcs11_aes_cbc()),
CipherAlgorithm::AesGcm(_) => {
let mut nonce = [0u8; 12];
session
.generate_random_slice(&mut nonce)
.map_err(|e| Error::Pkcs11(format!("C_GenerateRandom failed: {e}")))?;
let ct = {
let gcm_params =
cryptoki::mechanism::aead::GcmParams::new(&mut nonce, &[], 128.into())
.map_err(|e| Error::Pkcs11(format!("GcmParams::new failed: {e}")))?;
let mechanism = Mechanism::AesGcm(gcm_params);
session
.encrypt(&mechanism, self.key_handle, plaintext)
.map_err(|e| Error::Pkcs11(format!("C_Encrypt (AES-GCM) failed: {e}")))?
};
let mut result = Vec::with_capacity(12 + ct.len());
result.extend_from_slice(&nonce);
result.extend_from_slice(&ct);
Ok(result)
}
#[cfg(feature = "legacy")]
CipherAlgorithm::TripleDesCbc => Err(Error::unsupported(
Operation::Encrypt(self.algorithm),
"3DES-CBC not supported via PKCS#11 cipher",
)),
}
}
pub fn decrypt(&self, data: &[u8]) -> Result<Vec<u8>> {
crate::backend::require_fips_approved(Operation::Decrypt(self.algorithm))?;
let session = self
.session
.lock()
.map_err(|e| Error::Pkcs11(format!("session lock poisoned: {e}")))?;
match self.algorithm {
CipherAlgorithm::AesCbc(_) => Err(unsupported_pkcs11_aes_cbc()),
CipherAlgorithm::AesGcm(_) => {
if data.len() < 12 + 16 {
return Err(Error::Crypto(
"AES-GCM ciphertext too short (need nonce + tag)".into(),
));
}
let mut iv_buf = [0u8; 12];
iv_buf.copy_from_slice(&data[..12]);
let ct_and_tag = &data[12..];
let gcm_params =
cryptoki::mechanism::aead::GcmParams::new(&mut iv_buf, &[], 128.into())
.map_err(|e| Error::Pkcs11(format!("GcmParams::new failed: {e}")))?;
let mechanism = Mechanism::AesGcm(gcm_params);
session
.decrypt(&mechanism, self.key_handle, ct_and_tag)
.map_err(|e| Error::Pkcs11(format!("C_Decrypt (AES-GCM) failed: {e}")))
}
#[cfg(feature = "legacy")]
CipherAlgorithm::TripleDesCbc => Err(Error::unsupported(
Operation::Decrypt(self.algorithm),
"3DES-CBC not supported via PKCS#11 cipher",
)),
}
}
}
fn validate_pkcs11_cipher_algorithm(algorithm: CipherAlgorithm) -> Result<()> {
match algorithm {
CipherAlgorithm::AesCbc(_) => Err(unsupported_pkcs11_aes_cbc()),
CipherAlgorithm::AesGcm(_) => Ok(()),
#[cfg(feature = "legacy")]
CipherAlgorithm::TripleDesCbc => Err(Error::unsupported(
Operation::Encrypt(algorithm),
"3DES-CBC not supported via PKCS#11 cipher",
)),
}
}
fn unsupported_pkcs11_aes_cbc() -> Error {
Error::unsupported(
Operation::Encrypt(CipherAlgorithm::AesCbc(
crate::algorithm::AesKeySize::Aes128,
)),
"AES-CBC is unauthenticated and is not supported by the high-level PKCS#11 cipher; \
use an authenticated mode such as AES-GCM or a dedicated hazmat API"
.to_owned(),
)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::algorithm::{AesKeySize, OaepConfig};
fn assert_unsupported_operation<T>(result: Result<T>, expected: Operation) {
match result {
Err(Error::UnsupportedAlgorithm { operation, .. }) => {
assert_eq!(operation, expected);
}
Err(error) => panic!("expected UnsupportedAlgorithm, got: {error}"),
Ok(_) => panic!("expected UnsupportedAlgorithm, got success"),
}
}
#[test]
fn default_provider_selection_rejects_ambiguous_slots() {
let slot_one = Slot::try_from(1_u64).unwrap();
let slot_two = Slot::try_from(2_u64).unwrap();
let err = select_single_initialized_slot(&[slot_one, slot_two]).unwrap_err();
assert!(
err.to_string().contains("multiple initialized token slots"),
"got: {err}"
);
}
#[test]
fn default_provider_selection_accepts_one_slot() {
let slot = Slot::try_from(7_u64).unwrap();
assert_eq!(select_single_initialized_slot(&[slot]).unwrap().id(), 7);
}
#[test]
fn token_selector_rejects_ambiguous_matches() {
let slot_one = Slot::try_from(3_u64).unwrap();
let slot_two = Slot::try_from(4_u64).unwrap();
let err =
select_unique_matching_token(&[slot_one, slot_two], "prod-token", None).unwrap_err();
assert!(
err.to_string().contains("multiple initialized tokens"),
"got: {err}"
);
}
#[test]
fn pkcs11_cipher_rejects_aes_cbc_before_key_lookup() {
let err = validate_pkcs11_cipher_algorithm(CipherAlgorithm::AesCbc(AesKeySize::Aes128))
.unwrap_err();
assert!(err.to_string().contains("AES-CBC"), "got: {err}");
}
#[test]
fn pkcs11_cipher_accepts_aes_gcm_algorithm() {
assert!(
validate_pkcs11_cipher_algorithm(CipherAlgorithm::AesGcm(AesKeySize::Aes256)).is_ok()
);
}
#[test]
fn signature_mechanism_reports_callers_operation() {
for algorithm in [
SignatureAlgorithm::RsaPkcs1v15(HashAlgorithm::Sha3_256),
SignatureAlgorithm::RsaPss(HashAlgorithm::Sha3_256),
] {
for operation in [Operation::Sign(algorithm), Operation::Verify(algorithm)] {
assert_unsupported_operation(signature_mechanism(&algorithm, operation), operation);
}
}
}
#[test]
fn token_hashed_signatures_borrow_the_message() {
let data = [0x5a; 4096];
for algorithm in [
SignatureAlgorithm::RsaPkcs1v15(HashAlgorithm::Sha256),
SignatureAlgorithm::RsaPss(HashAlgorithm::Sha256),
SignatureAlgorithm::Ed25519,
SignatureAlgorithm::Hmac(HashAlgorithm::Sha256),
] {
let prepared = prepare_sign_data(&algorithm, &data).unwrap();
assert!(matches!(prepared, Cow::Borrowed(_)));
assert_eq!(prepared.as_ptr(), data.as_ptr());
assert_eq!(prepared.as_ref(), data);
}
}
#[test]
fn ecdsa_signatures_still_hash_the_message() {
crate::backend::initialize_backend().expect("backend initialization");
let data = b"message hashed before CKM_ECDSA";
let algorithm =
SignatureAlgorithm::Ecdsa(crate::algorithm::EcCurve::P256, HashAlgorithm::Sha256);
let prepared = prepare_sign_data(&algorithm, data).unwrap();
assert!(matches!(prepared, Cow::Owned(_)));
assert_eq!(
prepared.as_ref(),
crate::digest::digest(HashAlgorithm::Sha256, data).unwrap()
);
}
#[test]
#[ignore = "local message preparation measurement; run with --ignored --nocapture"]
fn signature_message_preparation_measurement() {
use std::hint::black_box;
use std::time::Instant;
fn baseline(algo: &SignatureAlgorithm, data: &[u8]) -> Result<Vec<u8>> {
match algo {
SignatureAlgorithm::Ecdsa(_, hash) => crate::digest::digest(*hash, data),
_ => Ok(data.to_vec()),
}
}
let algorithm = SignatureAlgorithm::RsaPss(HashAlgorithm::Sha256);
for (bytes, iterations) in [(64, 100_000), (4096, 50_000), (1_048_576, 500)] {
let message = vec![0x5a; bytes];
for sample in 1..=3 {
let modes = if sample % 2 == 0 {
[false, true]
} else {
[true, false]
};
for copied in modes {
let start = Instant::now();
for _ in 0..iterations {
if copied {
black_box(
baseline(black_box(&algorithm), black_box(&message)).unwrap(),
);
} else {
black_box(
prepare_sign_data(black_box(&algorithm), black_box(&message))
.unwrap(),
);
}
}
eprintln!(
"pkcs11_message_preparation sample={sample} baseline_copy={copied} \
bytes={bytes} iterations={iterations} ns_per_operation={:.3}",
start.elapsed().as_nanos() as f64 / iterations as f64
);
}
}
}
}
#[test]
fn temporary_secrets_are_destroyed_on_read_and_cleanup_errors() {
use std::cell::Cell;
for read_ok in [false, true] {
for destroy_ok in [false, true] {
let read_called = Cell::new(false);
let destroy_called = Cell::new(false);
let result = read_and_destroy_temporary_secret(
|| {
read_called.set(true);
if read_ok {
Ok(Zeroizing::new(vec![0x5a; 32]))
} else {
Err(Error::Pkcs11("synthetic attribute read failure".into()))
}
},
|| {
assert!(read_called.get());
destroy_called.set(true);
if destroy_ok {
Ok(())
} else {
Err(Error::Pkcs11(
"synthetic destroy failure; close the session to purge it".into(),
))
}
},
);
assert!(destroy_called.get());
match (read_ok, destroy_ok) {
(true, true) => assert_eq!(result.unwrap(), [0x5a; 32]),
(_, false) => assert!(result
.unwrap_err()
.to_string()
.contains("close the session to purge it")),
(false, true) => assert!(result
.unwrap_err()
.to_string()
.contains("attribute read failure")),
}
}
}
}
#[test]
#[ignore = "local mock session contention measurement; run with --ignored --nocapture"]
fn mock_session_contention_measurement() {
use std::sync::Barrier;
use std::time::{Duration, Instant};
const WORKERS: usize = 4;
const OPERATIONS_PER_WORKER: usize = 64;
for sample in 1..=3 {
for shared in [true, false] {
let sessions: Vec<_> = if shared {
let session = Arc::new(Mutex::new(0_usize));
vec![session; WORKERS]
} else {
(0..WORKERS)
.map(|_| Arc::new(Mutex::new(0_usize)))
.collect()
};
let start = Barrier::new(WORKERS + 1);
let elapsed = std::thread::scope(|scope| {
for session in &sessions {
let start = &start;
scope.spawn(move || {
start.wait();
for _ in 0..OPERATIONS_PER_WORKER {
let mut guard = session.lock().unwrap();
std::thread::sleep(Duration::from_millis(1));
*guard += 1;
}
});
}
let now = Instant::now();
start.wait();
now
})
.elapsed();
let operations = if shared {
*sessions[0].lock().unwrap()
} else {
sessions
.iter()
.map(|session| *session.lock().unwrap())
.sum()
};
assert_eq!(operations, WORKERS * OPERATIONS_PER_WORKER);
eprintln!(
"mock_session_contention sample={sample} shared_session={shared} \
workers={WORKERS} operations={operations} token_latency_ms=1 elapsed_ms={:.3}",
elapsed.as_secs_f64() * 1000.0
);
}
}
}
#[test]
fn oaep_mechanism_reports_encrypt_and_decrypt_operations() {
for config in [
OaepConfig {
digest: HashAlgorithm::Sha3_256,
mgf_digest: HashAlgorithm::Sha256,
},
OaepConfig {
digest: HashAlgorithm::Sha256,
mgf_digest: HashAlgorithm::Sha3_256,
},
] {
let algorithm = KeyTransportAlgorithm::RsaOaep(config);
for operation in [
Operation::TransportEncrypt(algorithm),
Operation::TransportDecrypt(algorithm),
] {
assert_unsupported_operation(
key_transport_mechanism(&algorithm, None, operation),
operation,
);
}
}
}
#[cfg(feature = "legacy")]
#[test]
fn keywrap_mechanism_reports_wrap_and_unwrap_operations() {
let algorithm = KeyWrapAlgorithm::TripleDesKw;
for operation in [Operation::Wrap(algorithm), Operation::Unwrap(algorithm)] {
assert_unsupported_operation(keywrap_mechanism(&algorithm, operation), operation);
}
}
#[test]
fn ec_params_named_curve_oids_map_to_fips_policy_curves() {
use crate::algorithm::EcCurve;
assert_eq!(ec_curve_for_ec_params(OID_DER_P256), Some(EcCurve::P256));
assert_eq!(ec_curve_for_ec_params(OID_DER_P384), Some(EcCurve::P384));
assert_eq!(ec_curve_for_ec_params(OID_DER_P521), Some(EcCurve::P521));
assert_eq!(
ec_curve_for_ec_params(&[0x06, 0x05, 0x2b, 0x81, 0x04, 0x00, 0x0a]),
None
);
assert_eq!(ec_curve_for_ec_params(&[]), None);
}
#[test]
fn keywrap_prefers_wrap_key_over_encrypt() {
assert_eq!(select_keywrap_call(true, true), KeyWrapCall::KeyManagement);
assert_eq!(select_keywrap_call(true, false), KeyWrapCall::KeyManagement);
assert_eq!(select_keywrap_call(false, true), KeyWrapCall::Cipher);
assert_eq!(select_keywrap_call(false, false), KeyWrapCall::Unsupported);
}
#[test]
fn keywrap_calls_follow_each_directions_flags() {
use cryptoki_sys::{CKF_DECRYPT, CKF_ENCRYPT, CKF_UNWRAP, CKF_WRAP, CK_MECHANISM_INFO};
use KeyWrapCall::{Cipher, KeyManagement, Unsupported};
let calls = |flags| {
keywrap_calls_for(&cryptoki::mechanism::MechanismInfo::from(
CK_MECHANISM_INFO {
ulMinKeySize: 16,
ulMaxKeySize: 32,
flags,
},
))
};
assert_eq!(calls(CKF_WRAP | CKF_UNWRAP), (KeyManagement, KeyManagement));
assert_eq!(calls(CKF_ENCRYPT | CKF_DECRYPT), (Cipher, Cipher));
assert_eq!(
calls(CKF_WRAP | CKF_UNWRAP | CKF_ENCRYPT | CKF_DECRYPT),
(KeyManagement, KeyManagement)
);
assert_eq!(calls(CKF_WRAP | CKF_DECRYPT), (KeyManagement, Cipher));
assert_eq!(calls(CKF_ENCRYPT | CKF_UNWRAP), (Cipher, KeyManagement));
assert_eq!(calls(CKF_WRAP), (KeyManagement, Unsupported));
assert_eq!(calls(CKF_DECRYPT), (Unsupported, Cipher));
assert_eq!(calls(0), (Unsupported, Unsupported));
}
fn token(serial: &str) -> TokenIdentity {
TokenIdentity {
module: ModuleIdentity::Path(PathBuf::from("/opt/hsm/libpkcs11.so")),
slot: 1,
serial: serial.to_owned(),
label: "token".to_owned(),
}
}
#[test]
fn login_record_accepts_only_the_recorded_pin() {
crate::backend::initialize_backend().expect("backend initialization");
let mut logins = LoginRegistry::default();
let verifier = logins.verifier(b"1234").unwrap();
assert_eq!(verifier.len(), 32);
assert_ne!(verifier.as_slice(), b"1234");
assert_eq!(logins.verifier(b"1234").unwrap(), verifier);
let mut record = LoginRecord::default();
let err = record.check(&verifier).unwrap_err();
assert!(
err.to_string().contains("logged in outside riptering"),
"got: {err}"
);
record.logged_in(verifier.clone());
assert!(record.check(&verifier).is_ok());
for wrong in [&b"12345"[..], b"123", b"4321", b""] {
let wrong = logins.verifier(wrong).unwrap();
for _ in 1..MAX_PIN_MISMATCHES {
let err = record.check(&wrong).unwrap_err();
assert!(err.to_string().contains("different PIN"), "got: {err}");
}
assert!(record.check(&verifier).is_ok());
}
let replacement = logins.verifier(b"5678").unwrap();
record.logged_in(replacement.clone());
assert!(record.check(&replacement).is_ok());
assert!(record.check(&verifier).is_err());
}
#[test]
fn login_record_refuses_everything_after_repeated_wrong_pins() {
crate::backend::initialize_backend().expect("backend initialization");
let mut logins = LoginRegistry::default();
let right = logins.verifier(b"1234").unwrap();
let wrong = logins.verifier(b"0000").unwrap();
let mut record = LoginRecord::default();
record.logged_in(right.clone());
for _ in 0..MAX_PIN_MISMATCHES {
let err = record.check(&wrong).unwrap_err();
assert!(err.to_string().contains("different PIN"), "got: {err}");
}
assert!(record.verifier.is_none());
for pin in [&right, &wrong] {
let err = record.check(pin).unwrap_err();
assert!(err.to_string().contains("3 wrong PINs"), "got: {err}");
}
record.logged_in(right.clone());
assert!(record.check(&right).is_ok());
}
#[test]
fn login_registry_forgets_the_pin_without_a_live_session() {
crate::backend::initialize_backend().expect("backend initialization");
let mut logins = LoginRegistry::default();
let right = logins.verifier(b"1234").unwrap();
let wrong = logins.verifier(b"0000").unwrap();
let record = logins.track(token("a"));
record.logged_in(right.clone());
assert!(record.check(&wrong).is_err());
assert_eq!(record.mismatches, 1);
let record = logins.track(token("a"));
assert!(record.verifier.is_none());
assert_eq!(record.mismatches, 0);
let err = record.check(&right).unwrap_err();
assert!(
err.to_string().contains("logged in outside riptering"),
"got: {err}"
);
logins.track(token("a")).logged_in(right.clone());
assert!(logins.tokens[&token("a")].verifier.is_some());
assert!(logins.track(token("b")).verifier.is_none());
}
#[test]
fn login_registry_keys_are_per_registry() {
crate::backend::initialize_backend().expect("backend initialization");
let first = LoginRegistry::default().verifier(b"1234").unwrap();
let second = LoginRegistry::default().verifier(b"1234").unwrap();
assert_ne!(first, second);
}
#[test]
fn module_identity_names_the_file() {
let manifest = Path::new(env!("CARGO_MANIFEST_DIR")).join("Cargo.toml");
let other_spelling = Path::new(env!("CARGO_MANIFEST_DIR"))
.join("src")
.join("..")
.join("Cargo.toml");
assert_eq!(module_identity(&manifest), module_identity(&other_spelling));
assert_ne!(
module_identity(&manifest),
module_identity(&Path::new(env!("CARGO_MANIFEST_DIR")).join("README.md"))
);
assert_eq!(
module_identity(Path::new("libriptering-missing-module.so")),
ModuleIdentity::Path(PathBuf::from("libriptering-missing-module.so"))
);
}
#[test]
fn kek_value_len_check() {
let len = |n: usize| Attribute::ValueLen(n.try_into().unwrap());
assert!(check_kek_value_len(&[len(32)], 32).is_ok());
assert!(check_kek_value_len(&[len(16)], 32).is_err());
assert!(check_kek_value_len(&[], 32).is_err());
assert!(check_kek_value_len(&[len(32), len(32)], 32).is_err());
}
#[test]
fn token_key_binding_requires_complete_unique_attributes() {
use crate::algorithm::EcCurve;
let aes = [
Attribute::KeyType(KeyType::AES),
Attribute::ValueLen(32.into()),
];
assert!(validate_token_key_attributes(&aes, TokenKey::Aes(AesKeySize::Aes256)).is_ok());
assert!(validate_token_key_attributes(&aes, TokenKey::Aes(AesKeySize::Aes128)).is_err());
assert!(validate_token_key_attributes(&aes, TokenKey::Rsa).is_err());
for attrs in [
vec![],
vec![aes[0].clone()],
vec![aes[1].clone()],
vec![aes[0].clone(), aes[0].clone(), aes[1].clone()],
] {
assert!(
validate_token_key_attributes(&attrs, TokenKey::Aes(AesKeySize::Aes256)).is_err()
);
}
for (curve, params) in [
(EcCurve::P256, OID_DER_P256),
(EcCurve::P384, OID_DER_P384),
(EcCurve::P521, OID_DER_P521),
] {
let attrs = [
Attribute::KeyType(KeyType::EC),
Attribute::EcParams(params.to_vec()),
];
assert!(validate_token_key_attributes(&attrs, TokenKey::Ec(curve)).is_ok());
for other in [EcCurve::P256, EcCurve::P384, EcCurve::P521] {
assert_eq!(
validate_token_key_attributes(&attrs, TokenKey::Ec(other)).is_ok(),
other == curve
);
}
assert!(validate_token_key_attributes(&attrs[..1], TokenKey::Ec(curve)).is_err());
}
}
#[test]
fn token_rsa_strength_uses_actual_modulus_not_claimed_bits() {
let modulus = |bits: usize| {
let mut n = vec![0; bits.div_ceil(8)];
n[0] = 1 << ((bits - 1) % 8);
*n.last_mut().unwrap() |= 1;
Attribute::Modulus(n)
};
for bits in [1024, 2047, 2048, 2049, 3072] {
let attrs = [
Attribute::KeyType(KeyType::RSA),
modulus(bits),
Attribute::ModulusBits(4096.into()),
];
let accepted = bits >= 2048 && (!cfg!(feature = "fips") || bits.is_multiple_of(2));
assert_eq!(
validate_token_key_attributes(&attrs, TokenKey::Rsa).is_ok(),
accepted
);
}
let mut n = vec![0x80; 256];
assert!(validate_token_key_attributes(
&[
Attribute::KeyType(KeyType::RSA),
Attribute::Modulus(n.clone())
],
TokenKey::Rsa
)
.is_err());
*n.last_mut().unwrap() |= 1;
n.insert(0, 0);
assert!(validate_token_key_attributes(
&[Attribute::KeyType(KeyType::RSA), Attribute::Modulus(n)],
TokenKey::Rsa
)
.is_ok());
assert!(validate_token_key_attributes(
&[
Attribute::KeyType(KeyType::RSA),
Attribute::ModulusBits(4096.into())
],
TokenKey::Rsa
)
.is_err());
}
#[test]
fn token_edwards_and_hmac_parameters_match_the_declared_family() {
for params in [
b"\x13\x0cedwards25519".as_slice(),
&[0x06, 0x03, 0x2b, 0x65, 0x70],
] {
assert!(validate_token_key_attributes(
&[
Attribute::KeyType(KeyType::EC_EDWARDS),
Attribute::EcParams(params.to_vec())
],
TokenKey::Ed25519
)
.is_ok());
}
assert!(validate_token_key_attributes(
&[
Attribute::KeyType(KeyType::EC_EDWARDS),
Attribute::EcParams(b"\x13\x0acurve25519".to_vec())
],
TokenKey::Ed25519
)
.is_err());
assert!(validate_token_key_attributes(
&[Attribute::KeyType(KeyType::GENERIC_SECRET)],
TokenKey::HmacSha256
)
.is_ok());
assert!(validate_token_key_attributes(
&[Attribute::KeyType(KeyType::SHA256_HMAC)],
TokenKey::HmacSha256
)
.is_err());
assert!(validate_token_key_attributes(
&[Attribute::KeyType(KeyType::AES)],
TokenKey::HmacSha256
)
.is_err());
}
#[test]
fn secret_attribute_extraction_rejects_duplicates_and_moves_owned_value() {
let bytes = vec![7; 32];
let ptr = bytes.as_ptr();
let extracted =
extract_secret_value(SecretAttributes(vec![Attribute::Value(bytes)]), "fixture")
.unwrap();
assert_eq!(extracted.as_ptr(), ptr);
assert_eq!(extracted.as_slice(), &[7; 32]);
assert!(extract_secret_value(SecretAttributes(vec![]), "fixture").is_err());
assert!(extract_secret_value(
SecretAttributes(vec![
Attribute::Value(vec![1; 32]),
Attribute::Value(vec![2; 32])
]),
"fixture"
)
.is_err());
}
}