Skip to main content

rite_sdk/
backend.rs

1//! Backend trait hierarchy for cryptographic hardware abstraction.
2//!
3//! ## Design philosophy
4//!
5//! Backends are **orthogonal to roles and participants**. They represent the
6//! execution mechanism (how an action is implemented), not the human assignment
7//! (who performs the action).
8//!
9//! The four layers of abstraction:
10//! - **Step**: Unit of execution in the workflow
11//! - **Role**: WHO performs the step (human/participant)
12//! - **Action**: WHAT semantic operation happens
13//! - **Backend**: HOW the action is implemented (software/hardware)
14//!
15//! ## Trait composition
16//!
17//! Backends implement only the traits they're capable of. There are no universal
18//! requirements: traits are composable based on backend capabilities.
19//!
20//! Example trait support matrix:
21//! - Software: `KeyStore + Sign + KeyTransport`
22//! - `YubiKey` PIV: `KeyStore + Sign + Piv + Yubikey`
23//! - PKCS#11 HSM: `KeyStore + Sign + KeyTransport + Pkcs11`
24//! - Mock: All traits (for testing)
25
26use std::collections::BTreeMap;
27
28use crate::key_material::PublicKeyDer;
29use crate::types::{
30    Attestation, CertRef, KeyId, KeyMetadata, KeySecurityAttributes, KeySpec, PcrValue,
31    PivDeviceInfo, PivSlot, PivSlotInfo, Pkcs11Mechanism, Pkcs11TokenInfo, SignAlgorithm, TpmInfo,
32    WrapAlgorithm, WrappedKey, YubikeySlotMetadata,
33};
34
35/// Implement the `as_*_mut` upcasting helpers on a backend struct.
36///
37/// Each `Backend` implementor must return `Some(self)` for every trait it
38/// supports and `None` for those it does not. Without this macro that is four
39/// lines of boilerplate per capability. The macro expands to those lines
40/// automatically and allows attaching per-capability doc comments.
41///
42/// # Usage
43///
44/// ```
45/// use rite_sdk::{
46///     backend_capabilities, Backend, BackendError, KeyId, RandomBackend, SignAlgorithm,
47///     SignBackend,
48/// };
49///
50/// struct MyBackend {
51///     name: String,
52/// }
53///
54/// impl Backend for MyBackend {
55///     fn name(&self) -> &str { &self.name }
56///     fn provider(&self) -> &str { "software" }
57///     fn fingerprint(&self) -> String { "sha256:...".to_string() }
58///
59///     backend_capabilities!(
60///         /// Signs with software-held RSA and ECDSA keys.
61///         as_sign_mut: SignBackend,
62///         /// Draws ceremony randomness from the OS CSPRNG.
63///         as_random_mut: RandomBackend,
64///     );
65/// }
66///
67/// impl SignBackend for MyBackend {
68///     fn sign(&mut self, _key: &KeyId, _msg: &[u8], _alg: SignAlgorithm)
69///         -> Result<Vec<u8>, BackendError> { Ok(Vec::new()) }
70/// }
71///
72/// impl RandomBackend for MyBackend {
73///     fn generate_random(&mut self, len: usize) -> Result<Vec<u8>, BackendError> {
74///         Ok(vec![0u8; len])
75///     }
76/// }
77/// ```
78///
79/// Capabilities not listed here are not overridden, so they inherit the trait's
80/// default doc ("Not supported by this backend.") and return `None`.
81///
82/// The trait names passed to the macro must be in scope at the call site.
83///
84/// Note: `#[macro_export]` places this macro at the crate root
85/// (`rite_sdk::backend_capabilities!`), not in the `backend` module.
86/// This is standard Rust macro scoping.
87#[macro_export]
88macro_rules! backend_capabilities {
89    ($($(#[$attr:meta])* $method:ident : $trait:ident),* $(,)?) => {
90        $(
91            $(#[$attr])*
92            fn $method(&mut self) -> Option<&mut dyn $trait> {
93                Some(self)
94            }
95        )*
96    };
97}
98
99/// Core trait that all backends must implement.
100///
101/// Provides identity and fingerprinting for audit trails.
102///
103/// # Capability downcasting
104///
105/// Rust does not allow downcasting from `dyn Backend` to `dyn KeyStoreBackend`
106/// (or any other subtrait) without explicit support. The `as_*_mut` methods
107/// implement this pattern manually: a backend returns `Some(self)` for each
108/// capability it supports, and `None` for those it does not.
109///
110/// Use the [`backend_capabilities!`] macro to generate these methods without
111/// boilerplate.
112pub trait Backend: Send + Sync {
113    /// Backend name (unique identifier within a ceremony).
114    fn name(&self) -> &str;
115
116    /// Backend provider name (e.g., "software", "yubikey", "pkcs11").
117    fn provider(&self) -> &str;
118
119    /// Fingerprint for audit evidence.
120    ///
121    /// Format examples:
122    /// - Software: `"software-backend=mybackend"`
123    /// - `YubiKey`: `"yubikey-serial=12345678+firmware=5.7.1"`
124    /// - PKCS#11: `"pkcs11-module=/usr/lib/softhsm2.so+slot=0"`
125    fn fingerprint(&self) -> String;
126
127    /// Not supported by this backend, always returns `None`.
128    fn as_keystore_mut(&mut self) -> Option<&mut dyn KeyStoreBackend> {
129        None
130    }
131
132    /// Not supported by this backend, always returns `None`.
133    fn as_sign_mut(&mut self) -> Option<&mut dyn SignBackend> {
134        None
135    }
136
137    /// Not supported by this backend, always returns `None`.
138    fn as_verify_mut(&mut self) -> Option<&mut dyn VerifyBackend> {
139        None
140    }
141
142    /// Not supported by this backend, always returns `None`.
143    fn as_attest_mut(&mut self) -> Option<&mut dyn AttestationBackend> {
144        None
145    }
146
147    /// Not supported by this backend, always returns `None`.
148    fn as_piv_mut(&mut self) -> Option<&mut dyn PivBackend> {
149        None
150    }
151
152    /// Not supported by this backend, always returns `None`.
153    fn as_yubikey_mut(&mut self) -> Option<&mut dyn YubikeyBackend> {
154        None
155    }
156
157    /// Not supported by this backend, always returns `None`.
158    fn as_transport_mut(&mut self) -> Option<&mut dyn KeyTransportBackend> {
159        None
160    }
161
162    /// Not supported by this backend, always returns `None`.
163    fn as_certstore_mut(&mut self) -> Option<&mut dyn CertStoreBackend> {
164        None
165    }
166
167    /// Not supported by this backend, always returns `None`.
168    fn as_random_mut(&mut self) -> Option<&mut dyn RandomBackend> {
169        None
170    }
171
172    /// Not supported by this backend, always returns `None`.
173    fn as_pkcs11_mut(&mut self) -> Option<&mut dyn Pkcs11Backend> {
174        None
175    }
176
177    /// Not supported by this backend, always returns `None`.
178    fn as_pkcs11_admin_mut(&mut self) -> Option<&mut dyn Pkcs11AdminBackend> {
179        None
180    }
181
182    /// Not supported by this backend, always returns `None`.
183    fn as_tpm_mut(&mut self) -> Option<&mut dyn TpmBackend> {
184        None
185    }
186}
187
188/// Key generation and storage operations.
189///
190/// Backends implementing this trait can generate, import, and manage
191/// cryptographic keys.
192pub trait KeyStoreBackend: Backend {
193    /// Generate a new key.
194    ///
195    /// The backend owns the key and returns metadata including an opaque
196    /// `KeyId` reference. For hardware backends, the private key never leaves
197    /// the device.
198    fn generate_key(&mut self, spec: KeySpec) -> Result<KeyMetadata, BackendError>;
199
200    /// Import an existing private key.
201    ///
202    /// Only supported by backends that allow key import (software, some HSMs).
203    /// Hardware security modules may reject key import for security reasons.
204    ///
205    /// `key_bytes` must be in PKCS#8 DER format.
206    fn import_private_key(
207        &mut self,
208        spec: KeySpec,
209        key_bytes: &[u8],
210    ) -> Result<KeyMetadata, BackendError>;
211
212    /// Export the public key for `key_id`.
213    fn export_public_key(&self, key_id: &KeyId) -> Result<PublicKeyDer, BackendError>;
214
215    /// List all keys managed by this backend.
216    fn list_keys(&self) -> Result<Vec<KeyMetadata>, BackendError>;
217
218    /// Delete a key permanently.
219    fn delete_key(&mut self, key_id: &KeyId) -> Result<(), BackendError>;
220}
221
222/// Signing operations.
223pub trait SignBackend: Backend {
224    /// Sign `message` with `key_id` using `algorithm`.
225    ///
226    /// Returns raw signature bytes (format depends on algorithm).
227    fn sign(
228        &mut self,
229        key_id: &KeyId,
230        message: &[u8],
231        algorithm: SignAlgorithm,
232    ) -> Result<Vec<u8>, BackendError>;
233}
234
235/// Signature verification operations.
236///
237/// Separate from [`SignBackend`] because the two need different things. Signing
238/// needs a secret, so only the device holding it can do the work; verification
239/// needs a public key, so anything can. A card that signs and never verifies
240/// implements one trait, and a verification-only backend (a notary, an enclave)
241/// implements the other without having to refuse `sign`.
242///
243/// Most ceremonies verify in software and name no backend. Naming one is for a
244/// deployment that requires the check inside a validated boundary, or a
245/// verifier that is not local.
246pub trait VerifyBackend: Backend {
247    /// Verify `signature` over `message` under `key`.
248    ///
249    /// Takes the key rather than a reference to one the backend holds, because
250    /// the evidence a ceremony checks usually comes from outside it: a CSR from
251    /// another party, a signature made on a card that is no longer present.
252    /// There is no handle to name for those.
253    ///
254    /// `&mut self` because a verifier may need a session to work in. PKCS#11
255    /// creates a public key object, verifies against it, and destroys it.
256    fn verify_public_key(
257        &mut self,
258        key: &PublicKeyDer,
259        message: &[u8],
260        signature: &[u8],
261        algorithm: SignAlgorithm,
262    ) -> Result<bool, BackendError>;
263}
264
265/// Hardware attestation operations.
266///
267/// Backends that support attestation can cryptographically prove that a key
268/// was generated on a specific hardware device.
269pub trait AttestationBackend: Backend {
270    /// Return a cryptographic proof that `key_id` was generated on this
271    /// backend's hardware and has specific properties.
272    fn attest_key(&self, key_id: &KeyId) -> Result<Attestation, BackendError>;
273}
274
275/// Certificate storage operations.
276///
277/// Backends implementing this trait can store, read, and delete X.509
278/// certificates by reference. This is a unified interface for certificate
279/// management across PIV (slot-based), PKCS#11 (label/ID-based), and
280/// software backends.
281pub trait CertStoreBackend: Backend {
282    /// Store a DER-encoded X.509 certificate.
283    fn store_cert(&mut self, cert_ref: &CertRef, cert_der: &[u8]) -> Result<(), BackendError>;
284
285    /// Read a DER-encoded X.509 certificate.
286    fn read_cert(&self, cert_ref: &CertRef) -> Result<Vec<u8>, BackendError>;
287
288    /// Delete a certificate.
289    fn delete_cert(&mut self, cert_ref: &CertRef) -> Result<(), BackendError>;
290}
291
292/// Hardware or software random number generation.
293///
294/// Backends implementing this trait can generate random bytes. For HSM
295/// backends, this uses the device's internal RNG (e.g., PKCS#11 `C_GenerateRandom`).
296pub trait RandomBackend: Backend {
297    /// Generate `len` random bytes.
298    fn generate_random(&mut self, len: usize) -> Result<Vec<u8>, BackendError>;
299}
300
301/// Key transport (wrapping) operations.
302pub trait KeyTransportBackend: Backend {
303    /// Wrap `key_id` using `wrapping_key_id` with `algorithm`.
304    fn wrap(
305        &mut self,
306        key_id: &KeyId,
307        wrapping_key_id: &KeyId,
308        algorithm: WrapAlgorithm,
309    ) -> Result<WrappedKey, BackendError>;
310
311    /// Unwrap `wrapped` using `unwrapping_key_id`, importing the result with `label`.
312    fn unwrap(
313        &mut self,
314        wrapped: &WrappedKey,
315        unwrapping_key_id: &KeyId,
316        label: &str,
317    ) -> Result<KeyMetadata, BackendError>;
318
319    /// Wrap `key_id` to an external recipient's public key.
320    ///
321    /// Default implementation returns `UnsupportedOperation`.
322    fn wrap_to_public(
323        &mut self,
324        key_id: &KeyId,
325        recipient_pub_key: &PublicKeyDer,
326        algorithm: WrapAlgorithm,
327    ) -> Result<WrappedKey, BackendError> {
328        let _ = (key_id, recipient_pub_key, algorithm);
329        Err(BackendError::UnsupportedOperation(
330            "wrap_to_public not supported".to_string(),
331        ))
332    }
333}
334
335/// PKCS#11 token operations.
336///
337/// Provides access to PKCS#11-specific functionality: token info, key
338/// security attributes, mechanism enumeration, and session login/logout.
339pub trait Pkcs11Backend: Backend {
340    /// Get token information (label, manufacturer, serial, flags).
341    fn token_info(&self) -> Result<Pkcs11TokenInfo, BackendError>;
342
343    /// Read security attributes for `key_id` (`CKA_ALWAYS_SENSITIVE`, etc.).
344    fn key_security_attributes(
345        &self,
346        key_id: &KeyId,
347    ) -> Result<KeySecurityAttributes, BackendError>;
348
349    /// List mechanisms supported by this token.
350    fn supported_mechanisms(&self) -> Result<Vec<Pkcs11Mechanism>, BackendError>;
351
352    /// Log in to the token with a PIN.
353    fn login(&mut self, pin: &[u8]) -> Result<(), BackendError>;
354
355    /// Log out of the token.
356    fn logout(&mut self) -> Result<(), BackendError>;
357}
358
359/// PKCS#11 Security Officer (SO) role operations.
360///
361/// These operations require SO authentication and are typically performed
362/// only during initial token setup.
363pub trait Pkcs11AdminBackend: Backend {
364    /// Initialize a token (`C_InitToken`).
365    fn init_token(&mut self, so_pin: &[u8], label: &str) -> Result<(), BackendError>;
366
367    /// Initialize the user PIN (`C_InitPIN`). Requires SO session.
368    fn init_user_pin(&mut self, so_pin: &[u8], user_pin: &[u8]) -> Result<(), BackendError>;
369}
370
371/// TPM 2.0 operations.
372///
373/// Provides TPM-specific functionality: device info, PCR operations, and
374/// quote generation.
375pub trait TpmBackend: Backend {
376    /// Get TPM information (version, manufacturer, firmware).
377    fn tpm_info(&mut self) -> Result<TpmInfo, BackendError>;
378
379    /// Read PCR (Platform Configuration Register) values for the given PCR indices.
380    ///
381    /// Returns a `BTreeMap` keyed by PCR index for deterministic ordering in transcripts.
382    fn read_pcrs(&mut self, pcrs: &[u8]) -> Result<BTreeMap<u8, PcrValue>, BackendError>;
383
384    /// Extend a PCR register with new data.
385    fn extend_pcr(&mut self, pcr: u8, data: &[u8]) -> Result<(), BackendError>;
386
387    /// Generate a TPM quote (signed attestation over PCR values).
388    fn generate_quote(&mut self, pcrs: &[u8], nonce: &[u8]) -> Result<Attestation, BackendError>;
389}
390
391/// Personal Identity Verification (PIV) smart card operations.
392///
393/// Models the PIV card application interface defined by FIPS 201-3 and
394/// NIST SP 800-73-5. Methods in this trait correspond to PIV card commands
395/// and data model operations; their semantics are defined by the standard
396/// and should not diverge.
397///
398/// Backends implementing `PivBackend` should also implement the generic traits
399/// (`KeyStoreBackend`, `SignBackend`) for interoperability with non-PIV-aware actions.
400///
401/// # Standard references
402/// - FIPS 201-3: Personal Identity Verification of Federal Employees and Contractors
403/// - NIST SP 800-73-5 Part 1: PIV Card Application Data Model
404/// - NIST SP 800-73-5 Part 2: PIV Card Application Card Command Interface
405/// - NIST SP 800-78-5: Cryptographic Algorithms and Key Sizes for PIV
406pub trait PivBackend: Backend {
407    /// List populated PIV slots with metadata.
408    ///
409    /// Enumerates key references defined in SP 800-73-5 Part 1, Table 4b.
410    fn list_slots(&self) -> Result<Vec<PivSlotInfo>, BackendError>;
411
412    /// Verify the PIV Card Application PIN.
413    ///
414    /// Required before private key operations on slots with PIN access rules.
415    /// The PIN is typically 6–8 digits (ASCII-encoded).
416    /// [SP 800-73-5 Part 2, §3.3: VERIFY command]
417    fn verify_pin(&mut self, pin: &[u8]) -> Result<(), BackendError>;
418
419    /// Change the PIV Card Application PIN.
420    ///
421    /// [SP 800-73-5 Part 2, §3.4: CHANGE REFERENCE DATA command]
422    fn change_pin(&mut self, current: &[u8], new: &[u8]) -> Result<(), BackendError>;
423
424    /// Get remaining PIN retry count before lockout.
425    fn pin_retries(&mut self) -> Result<u32, BackendError>;
426
427    /// Reset a blocked PIN using the PUK (PIN Unblocking Key).
428    ///
429    /// [SP 800-73-5 Part 2, §3.4: RESET RETRY COUNTER command]
430    fn unblock_pin(&mut self, puk: &[u8], new_pin: &[u8]) -> Result<(), BackendError>;
431
432    /// Return device identity (serial, firmware version, form factor).
433    ///
434    /// Not all fields are available on every PIV card. Serial number
435    /// retrieval is vendor-specific (not standardized by NIST SP 800-73).
436    fn device_info(&self) -> Result<PivDeviceInfo, BackendError>;
437}
438
439/// `YubiKey`-specific extensions to the PIV interface.
440///
441/// Operations in this trait are Yubico vendor extensions that go beyond
442/// the NIST SP 800-73 standard. They are specific to `YubiKey` hardware
443/// and will not work with generic PIV smart cards.
444///
445/// # Vendor references
446/// - Yubico PIV documentation: <https://developers.yubico.com/PIV/>
447/// - `YubiKey` attestation: key reference F9 (Yubico-proprietary)
448/// - Touch/PIN policy configuration: Yubico-proprietary extensions
449///   to the GENERATE ASYMMETRIC KEY PAIR command
450pub trait YubikeyBackend: PivBackend {
451    /// Generate an attestation certificate for a key in `slot`.
452    ///
453    /// Proves the key was generated on-device (not imported).
454    /// Uses `YubiKey`'s attestation key (slot F9) to sign a certificate
455    /// over the public key in the target slot.
456    /// This is a Yubico extension, not available on generic PIV cards.
457    fn attest_slot(&self, slot: PivSlot) -> Result<Vec<u8>, BackendError>;
458
459    /// Authenticate with the management key (3DES or AES).
460    ///
461    /// Required before administrative operations: key generation,
462    /// certificate import, PIN/PUK retry configuration.
463    fn authenticate_management(&mut self, mgm_key: &[u8]) -> Result<(), BackendError>;
464
465    /// Change the management key.
466    fn change_management_key(&mut self, current: &[u8], new: &[u8]) -> Result<(), BackendError>;
467
468    /// Get `YubiKey`-specific metadata for `slot`.
469    ///
470    /// Returns touch policy, PIN policy, and key origin: information
471    /// not available through standard PIV commands.
472    fn slot_metadata(&self, slot: PivSlot) -> Result<YubikeySlotMetadata, BackendError>;
473
474    /// Block PUK permanently (prevents PIN unblock forever).
475    ///
476    /// High-security operation: once blocked, a forgotten PIN requires
477    /// full device reset (destroying all keys). Irreversible.
478    fn block_puk(&mut self) -> Result<(), BackendError>;
479}
480
481/// Whether an error may be re-executed by retrying the step.
482///
483/// A `Transient` error means the world wasn't ready and the step's work did not
484/// happen, so re-running it is meaningful (token reinserted, cable reseated). A
485/// `Fatal` error means re-execution is either meaningless or unsafe: a procedural
486/// result, a broken definition, or a terminal device state.
487///
488/// The classification lives on the error type, not in the ceremony definition: a
489/// ceremony author must not be able to mark, say, a signature mismatch retriable.
490#[non_exhaustive]
491#[derive(Debug, Clone, Copy, PartialEq, Eq)]
492pub enum Retriability {
493    /// Environmental and recoverable; retrying the step may succeed.
494    Transient,
495    /// Not retriable; the run must stop or escalate.
496    Fatal,
497}
498
499impl Retriability {
500    /// Convenience predicate for callers that only need a yes/no answer.
501    #[must_use]
502    pub const fn is_retriable(self) -> bool {
503        matches!(self, Retriability::Transient)
504    }
505}
506
507/// Backend-specific errors.
508#[derive(Debug, thiserror::Error)]
509#[non_exhaustive]
510pub enum BackendError {
511    /// Authentication required (not logged in).
512    #[error("Authentication required")]
513    AuthRequired,
514
515    /// PIN required to access backend.
516    #[error("PIN required")]
517    PinRequired,
518
519    /// PIN verification failed (wrong PIN). Contains remaining retries if known.
520    #[error("PIN verification failed ({0} retries remaining)")]
521    PinFailed(u32),
522
523    /// PIN is blocked (too many failed attempts).
524    #[error("PIN blocked")]
525    PinBlocked,
526
527    /// User PIN not initialized (PKCS#11: `C_InitPIN` not yet called).
528    #[error("User PIN not initialized")]
529    UserPinNotInitialized,
530
531    /// Management key authentication required.
532    #[error("Management key authentication required")]
533    ManagementKeyRequired,
534
535    /// Key not found in backend storage.
536    #[error("Key not found: {0}")]
537    KeyNotFound(String),
538
539    /// Object not found (PKCS#11: `find_objects` returned empty).
540    #[error("Object not found: {0}")]
541    ObjectNotFound(String),
542
543    /// PIV slot is empty (no key or certificate).
544    #[error("Slot empty: {0}")]
545    SlotEmpty(String),
546
547    /// Backend storage capacity exceeded.
548    #[error("Capacity exceeded")]
549    CapacityExceeded,
550
551    /// Operation not permitted (key usage flags don't allow this).
552    #[error("Operation not permitted: {0}")]
553    OperationNotPermitted(String),
554
555    /// Attribute is read-only (e.g., tried to set `CKA_ALWAYS_SENSITIVE`).
556    #[error("Attribute is read-only: {0}")]
557    AttributeReadOnly(String),
558
559    /// Algorithm not supported by this backend.
560    #[error("Unsupported algorithm: {0}")]
561    UnsupportedAlgorithm(String),
562
563    /// Operation not supported by this backend.
564    #[error("Unsupported operation: {0}")]
565    UnsupportedOperation(String),
566
567    /// Token not present in slot.
568    #[error("Token not present")]
569    TokenNotPresent,
570
571    /// Hardware failure (device disconnected, I/O error, etc.).
572    #[error("Hardware failure: {0}")]
573    HardwareFailure(String),
574
575    /// Invalid key format or data.
576    #[error("Invalid key format: {0}")]
577    InvalidKeyFormat(String),
578
579    /// Invalid data (malformed input, unexpected encoding, etc.).
580    #[error("Invalid data: {0}")]
581    InvalidData(String),
582
583    /// Backend not found in registry.
584    #[error("Backend not found: {0}")]
585    NotFound(String),
586
587    /// Configuration error (missing or invalid settings).
588    #[error("Configuration error: {0}")]
589    Configuration(String),
590
591    /// Generic backend error.
592    #[error("Backend error: {0}")]
593    Other(String),
594}
595
596impl BackendError {
597    /// Classify whether a step that failed with this error may be retried.
598    ///
599    /// The match is exhaustive with no wildcard arm on purpose: adding a new
600    /// `BackendError` variant fails to compile until it is deliberately
601    /// classified here.
602    #[must_use]
603    pub fn retriability(&self) -> Retriability {
604        match self {
605            // Environmental: the world wasn't ready, the work didn't happen.
606            // The operator can fix the precondition (log in, enter a PIN,
607            // insert the right token, initialize the device) and retry.
608            BackendError::AuthRequired
609            | BackendError::PinRequired
610            | BackendError::PinFailed(_)
611            | BackendError::ManagementKeyRequired
612            | BackendError::UserPinNotInitialized
613            | BackendError::KeyNotFound(_)
614            | BackendError::ObjectNotFound(_)
615            | BackendError::SlotEmpty(_)
616            | BackendError::TokenNotPresent
617            | BackendError::HardwareFailure(_)
618            | BackendError::Configuration(_) => Retriability::Transient,
619
620            // Fatal: re-execution is meaningless or unsafe. A terminal device
621            // state (PIN budget exhausted, capacity full), a definition that
622            // asks for something the backend cannot do, or malformed/unknown
623            // input that retrying cannot resolve.
624            BackendError::PinBlocked
625            | BackendError::CapacityExceeded
626            | BackendError::OperationNotPermitted(_)
627            | BackendError::AttributeReadOnly(_)
628            | BackendError::UnsupportedAlgorithm(_)
629            | BackendError::UnsupportedOperation(_)
630            | BackendError::InvalidKeyFormat(_)
631            | BackendError::InvalidData(_)
632            | BackendError::NotFound(_)
633            | BackendError::Other(_) => Retriability::Fatal,
634        }
635    }
636}
637
638#[cfg(test)]
639mod tests {
640    use super::*;
641
642    #[test]
643    fn retriability_is_retriable_predicate() {
644        assert!(Retriability::Transient.is_retriable());
645        assert!(!Retriability::Fatal.is_retriable());
646    }
647
648    #[test]
649    fn environmental_errors_are_transient() {
650        let transient = [
651            BackendError::AuthRequired,
652            BackendError::PinRequired,
653            BackendError::PinFailed(2),
654            BackendError::ManagementKeyRequired,
655            BackendError::UserPinNotInitialized,
656            BackendError::KeyNotFound("k".into()),
657            BackendError::ObjectNotFound("o".into()),
658            BackendError::SlotEmpty("9a".into()),
659            BackendError::TokenNotPresent,
660            BackendError::HardwareFailure("cable".into()),
661            BackendError::Configuration("missing".into()),
662        ];
663        for err in transient {
664            assert_eq!(
665                err.retriability(),
666                Retriability::Transient,
667                "expected transient: {err:?}"
668            );
669        }
670    }
671
672    #[test]
673    fn fatal_errors_are_not_retriable() {
674        let fatal = [
675            BackendError::PinBlocked,
676            BackendError::CapacityExceeded,
677            BackendError::OperationNotPermitted("usage".into()),
678            BackendError::AttributeReadOnly("attr".into()),
679            BackendError::UnsupportedAlgorithm("rsa1".into()),
680            BackendError::UnsupportedOperation("wrap".into()),
681            BackendError::InvalidKeyFormat("pem".into()),
682            BackendError::InvalidData("bytes".into()),
683            BackendError::NotFound("backend".into()),
684            BackendError::Other("boom".into()),
685        ];
686        for err in fatal {
687            assert_eq!(
688                err.retriability(),
689                Retriability::Fatal,
690                "expected fatal: {err:?}"
691            );
692        }
693    }
694
695    #[test]
696    fn pin_failed_retriable_but_pin_blocked_terminal() {
697        // The hardware budget: a wrong PIN can be retried; an exhausted one cannot.
698        assert!(BackendError::PinFailed(1).retriability().is_retriable());
699        assert!(!BackendError::PinBlocked.retriability().is_retriable());
700    }
701}