use std::collections::BTreeMap;
use crate::types::{
Attestation, CertRef, KeyId, KeyMetadata, KeySecurityAttributes, KeySpec, PcrValue,
PivDeviceInfo, PivSlot, PivSlotInfo, Pkcs11Mechanism, Pkcs11TokenInfo, SignAlgorithm, TpmInfo,
WrapAlgorithm, WrappedKey, YubikeySlotMetadata,
};
#[macro_export]
macro_rules! backend_capabilities {
($($(#[$attr:meta])* $method:ident : $trait:ident),* $(,)?) => {
$(
$(#[$attr])*
fn $method(&mut self) -> Option<&mut dyn $trait> {
Some(self)
}
)*
};
}
pub trait Backend: Send + Sync {
fn name(&self) -> &str;
fn provider(&self) -> &str;
fn fingerprint(&self) -> String;
fn as_keystore_mut(&mut self) -> Option<&mut dyn KeyStoreBackend> {
None
}
fn as_sign_mut(&mut self) -> Option<&mut dyn SignBackend> {
None
}
fn as_attest_mut(&mut self) -> Option<&mut dyn AttestationBackend> {
None
}
fn as_piv_mut(&mut self) -> Option<&mut dyn PivBackend> {
None
}
fn as_yubikey_mut(&mut self) -> Option<&mut dyn YubikeyBackend> {
None
}
fn as_transport_mut(&mut self) -> Option<&mut dyn KeyTransportBackend> {
None
}
fn as_certstore_mut(&mut self) -> Option<&mut dyn CertStoreBackend> {
None
}
fn as_random_mut(&mut self) -> Option<&mut dyn RandomBackend> {
None
}
fn as_pkcs11_mut(&mut self) -> Option<&mut dyn Pkcs11Backend> {
None
}
fn as_pkcs11_admin_mut(&mut self) -> Option<&mut dyn Pkcs11AdminBackend> {
None
}
fn as_tpm_mut(&mut self) -> Option<&mut dyn TpmBackend> {
None
}
}
pub trait KeyStoreBackend: Backend {
fn generate_key(&mut self, spec: KeySpec) -> Result<KeyMetadata, BackendError>;
fn import_private_key(
&mut self,
spec: KeySpec,
key_bytes: &[u8],
) -> Result<KeyMetadata, BackendError>;
fn export_public_key(&self, key_id: &KeyId) -> Result<Vec<u8>, BackendError>;
fn list_keys(&self) -> Result<Vec<KeyMetadata>, BackendError>;
fn delete_key(&mut self, key_id: &KeyId) -> Result<(), BackendError>;
}
pub trait SignBackend: Backend {
fn sign(
&mut self,
key_id: &KeyId,
message: &[u8],
algorithm: SignAlgorithm,
) -> Result<Vec<u8>, BackendError>;
fn verify(
&self,
key_id: &KeyId,
message: &[u8],
signature: &[u8],
algorithm: SignAlgorithm,
) -> Result<bool, BackendError>;
}
pub trait AttestationBackend: Backend {
fn attest_key(&self, key_id: &KeyId) -> Result<Attestation, BackendError>;
}
pub trait CertStoreBackend: Backend {
fn store_cert(&mut self, cert_ref: &CertRef, cert_der: &[u8]) -> Result<(), BackendError>;
fn read_cert(&self, cert_ref: &CertRef) -> Result<Vec<u8>, BackendError>;
fn delete_cert(&mut self, cert_ref: &CertRef) -> Result<(), BackendError>;
}
pub trait RandomBackend: Backend {
fn generate_random(&mut self, len: usize) -> Result<Vec<u8>, BackendError>;
}
pub trait KeyTransportBackend: Backend {
fn wrap(
&mut self,
key_id: &KeyId,
wrapping_key_id: &KeyId,
algorithm: WrapAlgorithm,
) -> Result<WrappedKey, BackendError>;
fn unwrap(
&mut self,
wrapped: &WrappedKey,
unwrapping_key_id: &KeyId,
label: &str,
) -> Result<KeyMetadata, BackendError>;
fn wrap_to_public(
&mut self,
key_id: &KeyId,
recipient_pub_key: &[u8],
algorithm: WrapAlgorithm,
) -> Result<WrappedKey, BackendError> {
let _ = (key_id, recipient_pub_key, algorithm);
Err(BackendError::UnsupportedOperation(
"wrap_to_public not supported".to_string(),
))
}
}
pub trait Pkcs11Backend: Backend {
fn token_info(&self) -> Result<Pkcs11TokenInfo, BackendError>;
fn key_security_attributes(
&self,
key_id: &KeyId,
) -> Result<KeySecurityAttributes, BackendError>;
fn supported_mechanisms(&self) -> Result<Vec<Pkcs11Mechanism>, BackendError>;
fn login(&mut self, pin: &[u8]) -> Result<(), BackendError>;
fn logout(&mut self) -> Result<(), BackendError>;
}
pub trait Pkcs11AdminBackend: Backend {
fn init_token(&mut self, so_pin: &[u8], label: &str) -> Result<(), BackendError>;
fn init_user_pin(&mut self, so_pin: &[u8], user_pin: &[u8]) -> Result<(), BackendError>;
}
pub trait TpmBackend: Backend {
fn tpm_info(&mut self) -> Result<TpmInfo, BackendError>;
fn read_pcrs(&mut self, pcrs: &[u8]) -> Result<BTreeMap<u8, PcrValue>, BackendError>;
fn extend_pcr(&mut self, pcr: u8, data: &[u8]) -> Result<(), BackendError>;
fn generate_quote(&mut self, pcrs: &[u8], nonce: &[u8]) -> Result<Attestation, BackendError>;
}
pub trait PivBackend: Backend {
fn list_slots(&self) -> Result<Vec<PivSlotInfo>, BackendError>;
fn verify_pin(&mut self, pin: &[u8]) -> Result<(), BackendError>;
fn change_pin(&mut self, current: &[u8], new: &[u8]) -> Result<(), BackendError>;
fn pin_retries(&mut self) -> Result<u32, BackendError>;
fn unblock_pin(&mut self, puk: &[u8], new_pin: &[u8]) -> Result<(), BackendError>;
fn device_info(&self) -> Result<PivDeviceInfo, BackendError>;
}
pub trait YubikeyBackend: PivBackend {
fn attest_slot(&self, slot: PivSlot) -> Result<Vec<u8>, BackendError>;
fn authenticate_management(&mut self, mgm_key: &[u8]) -> Result<(), BackendError>;
fn change_management_key(&mut self, current: &[u8], new: &[u8]) -> Result<(), BackendError>;
fn slot_metadata(&self, slot: PivSlot) -> Result<YubikeySlotMetadata, BackendError>;
fn block_puk(&mut self) -> Result<(), BackendError>;
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Retriability {
Transient,
Fatal,
}
impl Retriability {
#[must_use]
pub const fn is_retriable(self) -> bool {
matches!(self, Retriability::Transient)
}
}
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum BackendError {
#[error("Authentication required")]
AuthRequired,
#[error("PIN required")]
PinRequired,
#[error("PIN verification failed ({0} retries remaining)")]
PinFailed(u32),
#[error("PIN blocked")]
PinBlocked,
#[error("User PIN not initialized")]
UserPinNotInitialized,
#[error("Management key authentication required")]
ManagementKeyRequired,
#[error("Key not found: {0}")]
KeyNotFound(String),
#[error("Object not found: {0}")]
ObjectNotFound(String),
#[error("Slot empty: {0}")]
SlotEmpty(String),
#[error("Capacity exceeded")]
CapacityExceeded,
#[error("Operation not permitted: {0}")]
OperationNotPermitted(String),
#[error("Attribute is read-only: {0}")]
AttributeReadOnly(String),
#[error("Unsupported algorithm: {0}")]
UnsupportedAlgorithm(String),
#[error("Unsupported operation: {0}")]
UnsupportedOperation(String),
#[error("Token not present")]
TokenNotPresent,
#[error("Hardware failure: {0}")]
HardwareFailure(String),
#[error("Invalid key format: {0}")]
InvalidKeyFormat(String),
#[error("Invalid data: {0}")]
InvalidData(String),
#[error("Backend not found: {0}")]
NotFound(String),
#[error("Configuration error: {0}")]
Configuration(String),
#[error("Backend error: {0}")]
Other(String),
}
impl BackendError {
#[must_use]
pub fn retriability(&self) -> Retriability {
match self {
BackendError::AuthRequired
| BackendError::PinRequired
| BackendError::PinFailed(_)
| BackendError::ManagementKeyRequired
| BackendError::UserPinNotInitialized
| BackendError::KeyNotFound(_)
| BackendError::ObjectNotFound(_)
| BackendError::SlotEmpty(_)
| BackendError::TokenNotPresent
| BackendError::HardwareFailure(_)
| BackendError::Configuration(_) => Retriability::Transient,
BackendError::PinBlocked
| BackendError::CapacityExceeded
| BackendError::OperationNotPermitted(_)
| BackendError::AttributeReadOnly(_)
| BackendError::UnsupportedAlgorithm(_)
| BackendError::UnsupportedOperation(_)
| BackendError::InvalidKeyFormat(_)
| BackendError::InvalidData(_)
| BackendError::NotFound(_)
| BackendError::Other(_) => Retriability::Fatal,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn retriability_is_retriable_predicate() {
assert!(Retriability::Transient.is_retriable());
assert!(!Retriability::Fatal.is_retriable());
}
#[test]
fn environmental_errors_are_transient() {
let transient = [
BackendError::AuthRequired,
BackendError::PinRequired,
BackendError::PinFailed(2),
BackendError::ManagementKeyRequired,
BackendError::UserPinNotInitialized,
BackendError::KeyNotFound("k".into()),
BackendError::ObjectNotFound("o".into()),
BackendError::SlotEmpty("9a".into()),
BackendError::TokenNotPresent,
BackendError::HardwareFailure("cable".into()),
BackendError::Configuration("missing".into()),
];
for err in transient {
assert_eq!(
err.retriability(),
Retriability::Transient,
"expected transient: {err:?}"
);
}
}
#[test]
fn fatal_errors_are_not_retriable() {
let fatal = [
BackendError::PinBlocked,
BackendError::CapacityExceeded,
BackendError::OperationNotPermitted("usage".into()),
BackendError::AttributeReadOnly("attr".into()),
BackendError::UnsupportedAlgorithm("rsa1".into()),
BackendError::UnsupportedOperation("wrap".into()),
BackendError::InvalidKeyFormat("pem".into()),
BackendError::InvalidData("bytes".into()),
BackendError::NotFound("backend".into()),
BackendError::Other("boom".into()),
];
for err in fatal {
assert_eq!(
err.retriability(),
Retriability::Fatal,
"expected fatal: {err:?}"
);
}
}
#[test]
fn pin_failed_retriable_but_pin_blocked_terminal() {
assert!(BackendError::PinFailed(1).retriability().is_retriable());
assert!(!BackendError::PinBlocked.retriability().is_retriable());
}
}