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, KeyAlgorithm, KeyId, KeyMetadata, KeyPolicy, KeySecurityAttributes,
31    KeySpec, PcrValue, PivDeviceInfo, PivSlot, PivSlotInfo, Pkcs11Mechanism, Pkcs11TokenInfo,
32    SignAlgorithm, TpmInfo, WrapScheme, 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.
302///
303/// Every method returns a [`WrappedKey`] carrying the scheme it was asked for
304/// and a [`WrapDescription`](crate::types::WrapDescription) of what it did, so
305/// the caller records an observation rather than a request.
306pub trait KeyTransportBackend: Backend {
307    /// Wrap `key_id` using `wrapping_key_id` under `scheme`.
308    fn wrap(
309        &mut self,
310        key_id: &KeyId,
311        wrapping_key_id: &KeyId,
312        scheme: WrapScheme,
313    ) -> Result<WrappedKey, BackendError>;
314
315    /// Unwrap `wrapped` using `unwrapping_key_id`, importing the result with
316    /// `label` under `policy`.
317    ///
318    /// The policy is the receiving ceremony's, not the origin's: nothing
319    /// travels with a wrapped key that says what it may do, so the step that
320    /// imports it declares that, exactly as the step that generated it did.
321    ///
322    /// `expected` is that same declaration one level down, because nothing
323    /// travels with a wrapped key saying what it *is* either. Recovered bytes
324    /// are a private key or a bare secret depending on what was wrapped, and
325    /// the two are told apart by what the receiving ceremony says it is
326    /// restoring rather than by what the bytes happen to parse as. `None` is a
327    /// ceremony that declared nothing, which an implementation may serve only
328    /// where the recovered form is unambiguous. A declaration that disagrees
329    /// with what came out is a failure, not something to reconcile.
330    fn unwrap(
331        &mut self,
332        wrapped: &WrappedKey,
333        unwrapping_key_id: &KeyId,
334        label: &str,
335        policy: KeyPolicy,
336        expected: Option<KeyAlgorithm>,
337    ) -> Result<KeyMetadata, BackendError>;
338
339    /// Wrap `key_id` to an external recipient's public key.
340    ///
341    /// Default implementation returns `UnsupportedOperation`.
342    fn wrap_to_public(
343        &mut self,
344        key_id: &KeyId,
345        recipient_pub_key: &PublicKeyDer,
346        scheme: WrapScheme,
347    ) -> Result<WrappedKey, BackendError> {
348        let _ = (key_id, recipient_pub_key, scheme);
349        Err(BackendError::UnsupportedOperation(
350            "wrap_to_public not supported".to_string(),
351        ))
352    }
353}
354
355/// PKCS#11 token operations.
356///
357/// Provides access to PKCS#11-specific functionality: token info, key
358/// security attributes, mechanism enumeration, and session login/logout.
359pub trait Pkcs11Backend: Backend {
360    /// Get token information (label, manufacturer, serial, flags).
361    fn token_info(&self) -> Result<Pkcs11TokenInfo, BackendError>;
362
363    /// Read security attributes for `key_id` (`CKA_ALWAYS_SENSITIVE`, etc.).
364    fn key_security_attributes(
365        &self,
366        key_id: &KeyId,
367    ) -> Result<KeySecurityAttributes, BackendError>;
368
369    /// List mechanisms supported by this token.
370    fn supported_mechanisms(&self) -> Result<Vec<Pkcs11Mechanism>, BackendError>;
371
372    /// Log in to the token with a PIN.
373    fn login(&mut self, pin: &[u8]) -> Result<(), BackendError>;
374
375    /// Log out of the token.
376    fn logout(&mut self) -> Result<(), BackendError>;
377}
378
379/// PKCS#11 Security Officer (SO) role operations.
380///
381/// These operations require SO authentication and are typically performed
382/// only during initial token setup.
383pub trait Pkcs11AdminBackend: Backend {
384    /// Initialize a token (`C_InitToken`).
385    fn init_token(&mut self, so_pin: &[u8], label: &str) -> Result<(), BackendError>;
386
387    /// Initialize the user PIN (`C_InitPIN`). Requires SO session.
388    fn init_user_pin(&mut self, so_pin: &[u8], user_pin: &[u8]) -> Result<(), BackendError>;
389}
390
391/// TPM 2.0 operations.
392///
393/// Provides TPM-specific functionality: device info, PCR operations, and
394/// quote generation.
395pub trait TpmBackend: Backend {
396    /// Get TPM information (version, manufacturer, firmware).
397    fn tpm_info(&mut self) -> Result<TpmInfo, BackendError>;
398
399    /// Read PCR (Platform Configuration Register) values for the given PCR indices.
400    ///
401    /// Returns a `BTreeMap` keyed by PCR index for deterministic ordering in transcripts.
402    fn read_pcrs(&mut self, pcrs: &[u8]) -> Result<BTreeMap<u8, PcrValue>, BackendError>;
403
404    /// Extend a PCR register with new data.
405    fn extend_pcr(&mut self, pcr: u8, data: &[u8]) -> Result<(), BackendError>;
406
407    /// Generate a TPM quote (signed attestation over PCR values).
408    fn generate_quote(&mut self, pcrs: &[u8], nonce: &[u8]) -> Result<Attestation, BackendError>;
409}
410
411/// Personal Identity Verification (PIV) smart card operations.
412///
413/// Models the PIV card application interface defined by FIPS 201-3 and
414/// NIST SP 800-73-5. Methods in this trait correspond to PIV card commands
415/// and data model operations; their semantics are defined by the standard
416/// and should not diverge.
417///
418/// Backends implementing `PivBackend` should also implement the generic traits
419/// (`KeyStoreBackend`, `SignBackend`) for interoperability with non-PIV-aware actions.
420///
421/// # Standard references
422/// - FIPS 201-3: Personal Identity Verification of Federal Employees and Contractors
423/// - NIST SP 800-73-5 Part 1: PIV Card Application Data Model
424/// - NIST SP 800-73-5 Part 2: PIV Card Application Card Command Interface
425/// - NIST SP 800-78-5: Cryptographic Algorithms and Key Sizes for PIV
426pub trait PivBackend: Backend {
427    /// List populated PIV slots with metadata.
428    ///
429    /// Enumerates key references defined in SP 800-73-5 Part 1, Table 4b.
430    fn list_slots(&self) -> Result<Vec<PivSlotInfo>, BackendError>;
431
432    /// Verify the PIV Card Application PIN.
433    ///
434    /// Required before private key operations on slots with PIN access rules.
435    /// The PIN is typically 6–8 digits (ASCII-encoded).
436    /// [SP 800-73-5 Part 2, §3.3: VERIFY command]
437    fn verify_pin(&mut self, pin: &[u8]) -> Result<(), BackendError>;
438
439    /// Change the PIV Card Application PIN.
440    ///
441    /// [SP 800-73-5 Part 2, §3.4: CHANGE REFERENCE DATA command]
442    fn change_pin(&mut self, current: &[u8], new: &[u8]) -> Result<(), BackendError>;
443
444    /// Get remaining PIN retry count before lockout.
445    fn pin_retries(&mut self) -> Result<u32, BackendError>;
446
447    /// Reset a blocked PIN using the PUK (PIN Unblocking Key).
448    ///
449    /// [SP 800-73-5 Part 2, §3.4: RESET RETRY COUNTER command]
450    fn unblock_pin(&mut self, puk: &[u8], new_pin: &[u8]) -> Result<(), BackendError>;
451
452    /// Return device identity (serial, firmware version, form factor).
453    ///
454    /// Not all fields are available on every PIV card. Serial number
455    /// retrieval is vendor-specific (not standardized by NIST SP 800-73).
456    fn device_info(&self) -> Result<PivDeviceInfo, BackendError>;
457}
458
459/// `YubiKey`-specific extensions to the PIV interface.
460///
461/// Operations in this trait are Yubico vendor extensions that go beyond
462/// the NIST SP 800-73 standard. They are specific to `YubiKey` hardware
463/// and will not work with generic PIV smart cards.
464///
465/// # Vendor references
466/// - Yubico PIV documentation: <https://developers.yubico.com/PIV/>
467/// - `YubiKey` attestation: key reference F9 (Yubico-proprietary)
468/// - Touch/PIN policy configuration: Yubico-proprietary extensions
469///   to the GENERATE ASYMMETRIC KEY PAIR command
470pub trait YubikeyBackend: PivBackend {
471    /// Generate an attestation certificate for a key in `slot`.
472    ///
473    /// Proves the key was generated on-device (not imported).
474    /// Uses `YubiKey`'s attestation key (slot F9) to sign a certificate
475    /// over the public key in the target slot.
476    /// This is a Yubico extension, not available on generic PIV cards.
477    fn attest_slot(&self, slot: PivSlot) -> Result<Vec<u8>, BackendError>;
478
479    /// Authenticate with the management key (3DES or AES).
480    ///
481    /// Required before administrative operations: key generation,
482    /// certificate import, PIN/PUK retry configuration.
483    fn authenticate_management(&mut self, mgm_key: &[u8]) -> Result<(), BackendError>;
484
485    /// Change the management key.
486    fn change_management_key(&mut self, current: &[u8], new: &[u8]) -> Result<(), BackendError>;
487
488    /// Get `YubiKey`-specific metadata for `slot`.
489    ///
490    /// Returns touch policy, PIN policy, and key origin: information
491    /// not available through standard PIV commands.
492    fn slot_metadata(&self, slot: PivSlot) -> Result<YubikeySlotMetadata, BackendError>;
493
494    /// Block PUK permanently (prevents PIN unblock forever).
495    ///
496    /// High-security operation: once blocked, a forgotten PIN requires
497    /// full device reset (destroying all keys). Irreversible.
498    fn block_puk(&mut self) -> Result<(), BackendError>;
499}
500
501/// Whether an error may be re-executed by retrying the step.
502///
503/// A `Transient` error means the world wasn't ready and the step's work did not
504/// happen, so re-running it is meaningful (token reinserted, cable reseated). A
505/// `Fatal` error means re-execution is either meaningless or unsafe: a procedural
506/// result, a broken definition, or a terminal device state.
507///
508/// The classification lives on the error type, not in the ceremony definition: a
509/// ceremony author must not be able to mark, say, a signature mismatch retriable.
510#[non_exhaustive]
511#[derive(Debug, Clone, Copy, PartialEq, Eq)]
512pub enum Retriability {
513    /// Environmental and recoverable; retrying the step may succeed.
514    Transient,
515    /// Not retriable; the run must stop or escalate.
516    Fatal,
517}
518
519impl Retriability {
520    /// Convenience predicate for callers that only need a yes/no answer.
521    #[must_use]
522    pub const fn is_retriable(self) -> bool {
523        matches!(self, Retriability::Transient)
524    }
525}
526
527/// Backend-specific errors.
528#[derive(Debug, thiserror::Error)]
529#[non_exhaustive]
530pub enum BackendError {
531    /// Authentication required (not logged in).
532    #[error("Authentication required")]
533    AuthRequired,
534
535    /// PIN required to access backend.
536    #[error("PIN required")]
537    PinRequired,
538
539    /// PIN verification failed (wrong PIN). Contains remaining retries if known.
540    #[error("PIN verification failed ({0} retries remaining)")]
541    PinFailed(u32),
542
543    /// PIN is blocked (too many failed attempts).
544    #[error("PIN blocked")]
545    PinBlocked,
546
547    /// User PIN not initialized (PKCS#11: `C_InitPIN` not yet called).
548    #[error("User PIN not initialized")]
549    UserPinNotInitialized,
550
551    /// Management key authentication required.
552    #[error("Management key authentication required")]
553    ManagementKeyRequired,
554
555    /// Key not found in backend storage.
556    #[error("Key not found: {0}")]
557    KeyNotFound(String),
558
559    /// Object not found (PKCS#11: `find_objects` returned empty).
560    #[error("Object not found: {0}")]
561    ObjectNotFound(String),
562
563    /// PIV slot is empty (no key or certificate).
564    #[error("Slot empty: {0}")]
565    SlotEmpty(String),
566
567    /// Backend storage capacity exceeded.
568    #[error("Capacity exceeded")]
569    CapacityExceeded,
570
571    /// Operation not permitted (key usage flags don't allow this).
572    #[error("Operation not permitted: {0}")]
573    OperationNotPermitted(String),
574
575    /// Attribute is read-only (e.g., tried to set `CKA_ALWAYS_SENSITIVE`).
576    #[error("Attribute is read-only: {0}")]
577    AttributeReadOnly(String),
578
579    /// Algorithm not supported by this backend.
580    #[error("Unsupported algorithm: {0}")]
581    UnsupportedAlgorithm(String),
582
583    /// Operation not supported by this backend.
584    #[error("Unsupported operation: {0}")]
585    UnsupportedOperation(String),
586
587    /// Token not present in slot.
588    #[error("Token not present")]
589    TokenNotPresent,
590
591    /// Hardware failure (device disconnected, I/O error, etc.).
592    #[error("Hardware failure: {0}")]
593    HardwareFailure(String),
594
595    /// Invalid key format or data.
596    #[error("Invalid key format: {0}")]
597    InvalidKeyFormat(String),
598
599    /// Invalid data (malformed input, unexpected encoding, etc.).
600    #[error("Invalid data: {0}")]
601    InvalidData(String),
602
603    /// Backend not found in registry.
604    #[error("Backend not found: {0}")]
605    NotFound(String),
606
607    /// Configuration error (missing or invalid settings).
608    #[error("Configuration error: {0}")]
609    Configuration(String),
610
611    /// Generic backend error.
612    #[error("Backend error: {0}")]
613    Other(String),
614}
615
616impl BackendError {
617    /// Classify whether a step that failed with this error may be retried.
618    ///
619    /// The match is exhaustive with no wildcard arm on purpose: adding a new
620    /// `BackendError` variant fails to compile until it is deliberately
621    /// classified here.
622    #[must_use]
623    pub fn retriability(&self) -> Retriability {
624        match self {
625            // Environmental: the world wasn't ready, the work didn't happen.
626            // The operator can fix the precondition (log in, enter a PIN,
627            // insert the right token, initialize the device) and retry.
628            BackendError::AuthRequired
629            | BackendError::PinRequired
630            | BackendError::PinFailed(_)
631            | BackendError::ManagementKeyRequired
632            | BackendError::UserPinNotInitialized
633            | BackendError::KeyNotFound(_)
634            | BackendError::ObjectNotFound(_)
635            | BackendError::SlotEmpty(_)
636            | BackendError::TokenNotPresent
637            | BackendError::HardwareFailure(_)
638            | BackendError::Configuration(_) => Retriability::Transient,
639
640            // Fatal: re-execution is meaningless or unsafe. A terminal device
641            // state (PIN budget exhausted, capacity full), a definition that
642            // asks for something the backend cannot do, or malformed/unknown
643            // input that retrying cannot resolve.
644            BackendError::PinBlocked
645            | BackendError::CapacityExceeded
646            | BackendError::OperationNotPermitted(_)
647            | BackendError::AttributeReadOnly(_)
648            | BackendError::UnsupportedAlgorithm(_)
649            | BackendError::UnsupportedOperation(_)
650            | BackendError::InvalidKeyFormat(_)
651            | BackendError::InvalidData(_)
652            | BackendError::NotFound(_)
653            | BackendError::Other(_) => Retriability::Fatal,
654        }
655    }
656}
657
658#[cfg(test)]
659mod tests {
660    use super::*;
661
662    #[test]
663    fn retriability_is_retriable_predicate() {
664        assert!(Retriability::Transient.is_retriable());
665        assert!(!Retriability::Fatal.is_retriable());
666    }
667
668    #[test]
669    fn environmental_errors_are_transient() {
670        let transient = [
671            BackendError::AuthRequired,
672            BackendError::PinRequired,
673            BackendError::PinFailed(2),
674            BackendError::ManagementKeyRequired,
675            BackendError::UserPinNotInitialized,
676            BackendError::KeyNotFound("k".into()),
677            BackendError::ObjectNotFound("o".into()),
678            BackendError::SlotEmpty("9a".into()),
679            BackendError::TokenNotPresent,
680            BackendError::HardwareFailure("cable".into()),
681            BackendError::Configuration("missing".into()),
682        ];
683        for err in transient {
684            assert_eq!(
685                err.retriability(),
686                Retriability::Transient,
687                "expected transient: {err:?}"
688            );
689        }
690    }
691
692    #[test]
693    fn fatal_errors_are_not_retriable() {
694        let fatal = [
695            BackendError::PinBlocked,
696            BackendError::CapacityExceeded,
697            BackendError::OperationNotPermitted("usage".into()),
698            BackendError::AttributeReadOnly("attr".into()),
699            BackendError::UnsupportedAlgorithm("rsa1".into()),
700            BackendError::UnsupportedOperation("wrap".into()),
701            BackendError::InvalidKeyFormat("pem".into()),
702            BackendError::InvalidData("bytes".into()),
703            BackendError::NotFound("backend".into()),
704            BackendError::Other("boom".into()),
705        ];
706        for err in fatal {
707            assert_eq!(
708                err.retriability(),
709                Retriability::Fatal,
710                "expected fatal: {err:?}"
711            );
712        }
713    }
714
715    #[test]
716    fn pin_failed_retriable_but_pin_blocked_terminal() {
717        // The hardware budget: a wrong PIN can be retried; an exhausted one cannot.
718        assert!(BackendError::PinFailed(1).retriability().is_retriable());
719        assert!(!BackendError::PinBlocked.retriability().is_retriable());
720    }
721}