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;
27use std::sync::{Mutex, MutexGuard};
28
29use zeroize::Zeroizing;
30
31use crate::key_material::PublicKeyDer;
32use crate::types::{
33    Attestation, CertRef, DataKey, KeyAlgorithm, KeyId, KeyMetadata, KeyPolicy, KeyProtection,
34    KeySecurityAttributes, KeySpec, PcrValue, PivDeviceInfo, PivSlot, PivSlotInfo, Pkcs11Mechanism,
35    Pkcs11TokenInfo, SignAlgorithm, TpmInfo, WrapScheme, WrappedKey, YubikeySlotMetadata,
36};
37
38/// Implement the `as_*_mut` upcasting helpers on a backend struct.
39///
40/// Each `Backend` implementor must return `Some(self)` for every trait it
41/// supports and `None` for those it does not. Without this macro that is four
42/// lines of boilerplate per capability. The macro expands to those lines
43/// automatically and allows attaching per-capability doc comments.
44///
45/// # Usage
46///
47/// ```
48/// use rite_sdk::{
49///     backend_capabilities, Backend, BackendError, KeyId, RandomBackend, SignAlgorithm,
50///     SignBackend,
51/// };
52///
53/// struct MyBackend {
54///     name: String,
55/// }
56///
57/// impl Backend for MyBackend {
58///     fn name(&self) -> &str { &self.name }
59///     fn provider(&self) -> &str { "software" }
60///     fn fingerprint(&self) -> String { "sha256:...".to_string() }
61///
62///     backend_capabilities!(
63///         /// Signs with software-held RSA and ECDSA keys.
64///         as_sign_mut: SignBackend,
65///         /// Draws ceremony randomness from the OS CSPRNG.
66///         as_random_mut: RandomBackend,
67///     );
68/// }
69///
70/// impl SignBackend for MyBackend {
71///     fn sign(&mut self, _key: &KeyId, _msg: &[u8], _alg: SignAlgorithm)
72///         -> Result<Vec<u8>, BackendError> { Ok(Vec::new()) }
73/// }
74///
75/// impl RandomBackend for MyBackend {
76///     fn generate_random(&mut self, len: usize) -> Result<Vec<u8>, BackendError> {
77///         Ok(vec![0u8; len])
78///     }
79/// }
80/// ```
81///
82/// Capabilities not listed here are not overridden, so they inherit the trait's
83/// default doc ("Not supported by this backend.") and return `None`.
84///
85/// The trait names passed to the macro must be in scope at the call site.
86///
87/// Note: `#[macro_export]` places this macro at the crate root
88/// (`rite_sdk::backend_capabilities!`), not in the `backend` module.
89/// This is standard Rust macro scoping.
90#[macro_export]
91macro_rules! backend_capabilities {
92    ($($(#[$attr:meta])* $method:ident : $trait:ident),* $(,)?) => {
93        $(
94            $(#[$attr])*
95            fn $method(&mut self) -> Option<&mut dyn $trait> {
96                Some(self)
97            }
98        )*
99    };
100}
101
102/// Core trait that all backends must implement.
103///
104/// Provides identity and fingerprinting for audit trails.
105///
106/// # Capability downcasting
107///
108/// Rust does not allow downcasting from `dyn Backend` to `dyn KeyStoreBackend`
109/// (or any other subtrait) without explicit support. The `as_*_mut` methods
110/// implement this pattern manually: a backend returns `Some(self)` for each
111/// capability it supports, and `None` for those it does not.
112///
113/// Use the [`backend_capabilities!`] macro to generate these methods without
114/// boilerplate.
115pub trait Backend: Send + Sync {
116    /// Backend name (unique identifier within a ceremony).
117    fn name(&self) -> &str;
118
119    /// Backend provider name (e.g., "software", "yubikey", "pkcs11").
120    fn provider(&self) -> &str;
121
122    /// Fingerprint for audit evidence.
123    ///
124    /// Format examples:
125    /// - Software: `"software-backend=mybackend"`
126    /// - `YubiKey`: `"yubikey-serial=12345678+firmware=5.7.1"`
127    /// - PKCS#11: `"pkcs11-module=/usr/lib/softhsm2.so+slot=0"`
128    fn fingerprint(&self) -> String;
129
130    /// Not supported by this backend, always returns `None`.
131    fn as_keystore_mut(&mut self) -> Option<&mut dyn KeyStoreBackend> {
132        None
133    }
134
135    /// Not supported by this backend, always returns `None`.
136    fn as_sign_mut(&mut self) -> Option<&mut dyn SignBackend> {
137        None
138    }
139
140    /// Not supported by this backend, always returns `None`.
141    fn as_verify_mut(&mut self) -> Option<&mut dyn VerifyBackend> {
142        None
143    }
144
145    /// Not supported by this backend, always returns `None`.
146    fn as_attest_mut(&mut self) -> Option<&mut dyn AttestationBackend> {
147        None
148    }
149
150    /// Not supported by this backend, always returns `None`.
151    fn as_piv_mut(&mut self) -> Option<&mut dyn PivBackend> {
152        None
153    }
154
155    /// Not supported by this backend, always returns `None`.
156    fn as_yubikey_mut(&mut self) -> Option<&mut dyn YubikeyBackend> {
157        None
158    }
159
160    /// Not supported by this backend, always returns `None`.
161    fn as_transport_mut(&mut self) -> Option<&mut dyn KeyTransportBackend> {
162        None
163    }
164
165    /// Not supported by this backend, always returns `None`.
166    fn as_certstore_mut(&mut self) -> Option<&mut dyn CertStoreBackend> {
167        None
168    }
169
170    /// Not supported by this backend, always returns `None`.
171    fn as_random_mut(&mut self) -> Option<&mut dyn RandomBackend> {
172        None
173    }
174
175    /// Not supported by this backend, always returns `None`.
176    fn as_pkcs11_mut(&mut self) -> Option<&mut dyn Pkcs11Backend> {
177        None
178    }
179
180    /// Not supported by this backend, always returns `None`.
181    fn as_pkcs11_admin_mut(&mut self) -> Option<&mut dyn Pkcs11AdminBackend> {
182        None
183    }
184
185    /// Not supported by this backend, always returns `None`.
186    fn as_tpm_mut(&mut self) -> Option<&mut dyn TpmBackend> {
187        None
188    }
189}
190
191/// Key generation and storage operations.
192///
193/// Backends implementing this trait can generate, import, and manage
194/// cryptographic keys.
195pub trait KeyStoreBackend: Backend {
196    /// Generate a new key.
197    ///
198    /// The backend owns the key and returns metadata including an opaque
199    /// `KeyId` reference. For hardware backends, the private key never leaves
200    /// the device.
201    fn generate_key(&mut self, spec: KeySpec) -> Result<KeyMetadata, BackendError>;
202
203    /// Import an existing key from material the caller holds.
204    ///
205    /// `spec.algorithm` says how to read `key_bytes`, because nothing in the
206    /// bytes distinguishes a 32-byte secret from any other 32 bytes: a symmetric
207    /// algorithm takes the raw key, every other one takes a private key, as
208    /// PKCS#8 DER or as PEM. The metadata that comes back carries a check value
209    /// in the first case and a public key in the second, which is the identity
210    /// each kind has.
211    ///
212    /// `passphrase` opens a private key that is encrypted, in any encoding the
213    /// backend reads. Material that needs one and is given none is refused by
214    /// name, and so is a passphrase given for material that is not encrypted:
215    /// a passphrase the transcript says was used has to have been used.
216    ///
217    /// Only supported by backends that allow key import (software, some HSMs).
218    /// Hardware security modules may reject it for security reasons, and a
219    /// device that holds only one kind of key refuses the other by name.
220    fn import_key(
221        &mut self,
222        spec: KeySpec,
223        key_bytes: &[u8],
224        passphrase: Option<&[u8]>,
225    ) -> Result<KeyMetadata, BackendError>;
226
227    /// Export the public key for `key_id`.
228    fn export_public_key(&self, key_id: &KeyId) -> Result<PublicKeyDer, BackendError>;
229
230    /// List all keys managed by this backend.
231    fn list_keys(&self) -> Result<Vec<KeyMetadata>, BackendError>;
232
233    /// Delete a key permanently.
234    fn delete_key(&mut self, key_id: &KeyId) -> Result<(), BackendError>;
235}
236
237/// Signing operations.
238pub trait SignBackend: Backend {
239    /// Sign `message` with `key_id` using `algorithm`.
240    ///
241    /// Returns raw signature bytes (format depends on algorithm).
242    fn sign(
243        &mut self,
244        key_id: &KeyId,
245        message: &[u8],
246        algorithm: SignAlgorithm,
247    ) -> Result<Vec<u8>, BackendError>;
248}
249
250/// Signature verification operations.
251///
252/// Separate from [`SignBackend`] because the two need different things. Signing
253/// needs a secret, so only the device holding it can do the work; verification
254/// needs a public key, so anything can. A card that signs and never verifies
255/// implements one trait, and a verification-only backend (a notary, an enclave)
256/// implements the other without having to refuse `sign`.
257///
258/// Most ceremonies verify in software and name no backend. Naming one is for a
259/// deployment that requires the check inside a validated boundary, or a
260/// verifier that is not local.
261pub trait VerifyBackend: Backend {
262    /// Verify `signature` over `message` under `key`.
263    ///
264    /// Takes the key rather than a reference to one the backend holds, because
265    /// the evidence a ceremony checks usually comes from outside it: a CSR from
266    /// another party, a signature made on a card that is no longer present.
267    /// There is no handle to name for those.
268    ///
269    /// `&mut self` because a verifier may need a session to work in. PKCS#11
270    /// creates a public key object, verifies against it, and destroys it.
271    fn verify_public_key(
272        &mut self,
273        key: &PublicKeyDer,
274        message: &[u8],
275        signature: &[u8],
276        algorithm: SignAlgorithm,
277    ) -> Result<bool, BackendError>;
278}
279
280/// Hardware attestation operations.
281///
282/// Backends that support attestation can cryptographically prove that a key
283/// was generated on a specific hardware device.
284pub trait AttestationBackend: Backend {
285    /// Return a cryptographic proof that `key_id` was generated on this
286    /// backend's hardware and has specific properties.
287    fn attest_key(&self, key_id: &KeyId) -> Result<Attestation, BackendError>;
288}
289
290/// Certificate storage operations.
291///
292/// Backends implementing this trait can store, read, and delete X.509
293/// certificates by reference. This is a unified interface for certificate
294/// management across PIV (slot-based), PKCS#11 (label/ID-based), and
295/// software backends.
296pub trait CertStoreBackend: Backend {
297    /// Store a DER-encoded X.509 certificate.
298    fn store_cert(&mut self, cert_ref: &CertRef, cert_der: &[u8]) -> Result<(), BackendError>;
299
300    /// Read a DER-encoded X.509 certificate.
301    fn read_cert(&self, cert_ref: &CertRef) -> Result<Vec<u8>, BackendError>;
302
303    /// Delete a certificate.
304    fn delete_cert(&mut self, cert_ref: &CertRef) -> Result<(), BackendError>;
305}
306
307/// Hardware or software random number generation.
308///
309/// Backends implementing this trait can generate random bytes. For HSM
310/// backends, this uses the device's internal RNG (e.g., PKCS#11 `C_GenerateRandom`).
311pub trait RandomBackend: Backend {
312    /// Generate `len` random bytes.
313    fn generate_random(&mut self, len: usize) -> Result<Vec<u8>, BackendError>;
314}
315
316/// Key transport (wrapping) operations.
317///
318/// Every method returns a [`WrappedKey`] carrying the scheme it was asked for
319/// and a [`WrapDescription`](crate::types::WrapDescription) of what it did, so
320/// the caller records an observation rather than a request.
321pub trait KeyTransportBackend: Backend {
322    /// Wrap `key_id` using `wrapping_key_id` under `scheme`.
323    fn wrap(
324        &mut self,
325        key_id: &KeyId,
326        wrapping_key_id: &KeyId,
327        scheme: WrapScheme,
328    ) -> Result<WrappedKey, BackendError>;
329
330    /// Unwrap `wrapped` using `unwrapping_key_id`, importing the result with
331    /// `label` under `policy`.
332    ///
333    /// The policy is the receiving ceremony's, not the origin's: nothing
334    /// travels with a wrapped key that says what it may do, so the step that
335    /// imports it declares that, exactly as the step that generated it did.
336    ///
337    /// `expected` is that same declaration one level down, because nothing
338    /// travels with a wrapped key saying what it *is* either. Recovered bytes
339    /// are a private key or a bare secret depending on what was wrapped, and
340    /// the two are told apart by what the receiving ceremony says it is
341    /// restoring rather than by what the bytes happen to parse as. `None` is a
342    /// ceremony that declared nothing, which an implementation may serve only
343    /// where the recovered form is unambiguous. A declaration that disagrees
344    /// with what came out is a failure, not something to reconcile.
345    fn unwrap(
346        &mut self,
347        wrapped: &WrappedKey,
348        unwrapping_key_id: &KeyId,
349        label: &str,
350        policy: KeyPolicy,
351        expected: Option<KeyAlgorithm>,
352    ) -> Result<KeyMetadata, BackendError>;
353
354    /// Produce a fresh content-encryption key protected by `kek`.
355    ///
356    /// Two copies of one key come back: the plaintext, for the caller to
357    /// encrypt content with and then drop, and a copy only `kek` opens, which
358    /// the container carries to the recipient.
359    ///
360    /// Content does not appear here. No key-protection
361    /// device Rite talks to is a cipher you hand a file to: a cloud KMS caps a
362    /// direct encrypt at a few kilobytes, a TPM takes a kilobyte per call over
363    /// a slow bus, and a smart card is slower still. Every one of them offers
364    /// this operation instead, so a backend implements what it can actually do
365    /// and the content pipeline stays in one place above it.
366    ///
367    /// `algorithm` must be a symmetric one, since a data key is a secret the
368    /// caller encrypts with directly. It is the length request AWS KMS spells
369    /// `KeySpec: AES_256 | AES_128`.
370    ///
371    /// The result carries a [`KeyProtection`] because the caller has to declare
372    /// the key-encryption algorithm in the container it assembles, and only the
373    /// backend knows which one it used.
374    ///
375    /// # Not yet a parameter
376    ///
377    /// An **encryption context**, which AWS calls `EncryptionContext` and Azure
378    /// calls `aad`, binds a data key to the purpose it was issued for: the same
379    /// value must be supplied to open it again, or the open fails. AWS
380    /// recommends it on every call. It is absent here because supplying it would
381    /// oblige the ceremony to store it somewhere a later run can read it back,
382    /// and CMS has no slot for it that the tag covers except `authAttrs`, which
383    /// is deferred. Adding it is the next widening this pair needs, and it wants
384    /// the transcript question answered first.
385    fn generate_data_key(
386        &mut self,
387        kek: &KeyId,
388        algorithm: KeyAlgorithm,
389    ) -> Result<DataKey, BackendError>;
390
391    /// Recover a data key from the protected copy a container carries.
392    ///
393    /// The counterpart of [`generate_data_key`](Self::generate_data_key), and
394    /// the half of the pair almost every backend implements: protecting a data
395    /// key needs only a public key or a shared secret, while opening one always
396    /// needs the device that holds the other half.
397    ///
398    /// `protection` says how `wrapped` was protected, taken from the container
399    /// rather than assumed, because the bytes say nothing about themselves and
400    /// the backend may implement several. An implementation that was not asked
401    /// which one to use could only guess, and a wrong guess on key material is
402    /// silent. A backend refuses a protection it does not implement rather than
403    /// decrypting under an assumption.
404    fn open_data_key(
405        &mut self,
406        kek: &KeyId,
407        wrapped: &[u8],
408        protection: KeyProtection,
409    ) -> Result<Zeroizing<Vec<u8>>, BackendError>;
410
411    /// Wrap `key_id` to an external recipient's public key.
412    ///
413    /// Default implementation returns `UnsupportedOperation`.
414    fn wrap_to_public(
415        &mut self,
416        key_id: &KeyId,
417        recipient_pub_key: &PublicKeyDer,
418        scheme: WrapScheme,
419    ) -> Result<WrappedKey, BackendError> {
420        let _ = (key_id, recipient_pub_key, scheme);
421        Err(BackendError::UnsupportedOperation(
422            "wrap_to_public not supported".to_string(),
423        ))
424    }
425}
426
427/// PKCS#11 token operations.
428///
429/// Provides access to PKCS#11-specific functionality: token info, key
430/// security attributes, mechanism enumeration, and session login/logout.
431pub trait Pkcs11Backend: Backend {
432    /// Get token information (label, manufacturer, serial, flags).
433    fn token_info(&self) -> Result<Pkcs11TokenInfo, BackendError>;
434
435    /// Read security attributes for `key_id` (`CKA_ALWAYS_SENSITIVE`, etc.).
436    fn key_security_attributes(
437        &self,
438        key_id: &KeyId,
439    ) -> Result<KeySecurityAttributes, BackendError>;
440
441    /// List mechanisms supported by this token.
442    fn supported_mechanisms(&self) -> Result<Vec<Pkcs11Mechanism>, BackendError>;
443
444    /// Log in to the token with a PIN.
445    fn login(&mut self, pin: &[u8]) -> Result<(), BackendError>;
446
447    /// Log out of the token.
448    fn logout(&mut self) -> Result<(), BackendError>;
449}
450
451/// PKCS#11 Security Officer (SO) role operations.
452///
453/// These operations require SO authentication and are typically performed
454/// only during initial token setup.
455pub trait Pkcs11AdminBackend: Backend {
456    /// Initialize a token (`C_InitToken`).
457    fn init_token(&mut self, so_pin: &[u8], label: &str) -> Result<(), BackendError>;
458
459    /// Initialize the user PIN (`C_InitPIN`). Requires SO session.
460    fn init_user_pin(&mut self, so_pin: &[u8], user_pin: &[u8]) -> Result<(), BackendError>;
461}
462
463/// TPM 2.0 operations.
464///
465/// Provides TPM-specific functionality: device info, PCR operations, and
466/// quote generation.
467pub trait TpmBackend: Backend {
468    /// Get TPM information (version, manufacturer, firmware).
469    fn tpm_info(&mut self) -> Result<TpmInfo, BackendError>;
470
471    /// Read PCR (Platform Configuration Register) values for the given PCR indices.
472    ///
473    /// Returns a `BTreeMap` keyed by PCR index for deterministic ordering in transcripts.
474    fn read_pcrs(&mut self, pcrs: &[u8]) -> Result<BTreeMap<u8, PcrValue>, BackendError>;
475
476    /// Extend a PCR register with new data.
477    fn extend_pcr(&mut self, pcr: u8, data: &[u8]) -> Result<(), BackendError>;
478
479    /// Generate a TPM quote (signed attestation over PCR values).
480    fn generate_quote(&mut self, pcrs: &[u8], nonce: &[u8]) -> Result<Attestation, BackendError>;
481}
482
483/// Personal Identity Verification (PIV) smart card operations.
484///
485/// Models the PIV card application interface defined by FIPS 201-3 and
486/// NIST SP 800-73-5. Methods in this trait correspond to PIV card commands
487/// and data model operations; their semantics are defined by the standard
488/// and should not diverge.
489///
490/// Backends implementing `PivBackend` should also implement the generic traits
491/// (`KeyStoreBackend`, `SignBackend`) for interoperability with non-PIV-aware actions.
492///
493/// # Standard references
494/// - FIPS 201-3: Personal Identity Verification of Federal Employees and Contractors
495/// - NIST SP 800-73-5 Part 1: PIV Card Application Data Model
496/// - NIST SP 800-73-5 Part 2: PIV Card Application Card Command Interface
497/// - NIST SP 800-78-5: Cryptographic Algorithms and Key Sizes for PIV
498pub trait PivBackend: Backend {
499    /// List populated PIV slots with metadata.
500    ///
501    /// Enumerates key references defined in SP 800-73-5 Part 1, Table 4b.
502    fn list_slots(&self) -> Result<Vec<PivSlotInfo>, BackendError>;
503
504    /// Verify the PIV Card Application PIN.
505    ///
506    /// Required before private key operations on slots with PIN access rules.
507    /// The PIN is typically 6–8 digits (ASCII-encoded).
508    /// [SP 800-73-5 Part 2, §3.3: VERIFY command]
509    fn verify_pin(&mut self, pin: &[u8]) -> Result<(), BackendError>;
510
511    /// Change the PIV Card Application PIN.
512    ///
513    /// [SP 800-73-5 Part 2, §3.4: CHANGE REFERENCE DATA command]
514    fn change_pin(&mut self, current: &[u8], new: &[u8]) -> Result<(), BackendError>;
515
516    /// Get remaining PIN retry count before lockout.
517    fn pin_retries(&mut self) -> Result<u32, BackendError>;
518
519    /// Reset a blocked PIN using the PUK (PIN Unblocking Key).
520    ///
521    /// [SP 800-73-5 Part 2, §3.4: RESET RETRY COUNTER command]
522    fn unblock_pin(&mut self, puk: &[u8], new_pin: &[u8]) -> Result<(), BackendError>;
523
524    /// Return device identity (serial, firmware version, form factor).
525    ///
526    /// Not all fields are available on every PIV card. Serial number
527    /// retrieval is vendor-specific (not standardized by NIST SP 800-73).
528    fn device_info(&self) -> Result<PivDeviceInfo, BackendError>;
529}
530
531/// `YubiKey`-specific extensions to the PIV interface.
532///
533/// Operations in this trait are Yubico vendor extensions that go beyond
534/// the NIST SP 800-73 standard. They are specific to `YubiKey` hardware
535/// and will not work with generic PIV smart cards.
536///
537/// # Vendor references
538/// - Yubico PIV documentation: <https://developers.yubico.com/PIV/>
539/// - `YubiKey` attestation: key reference F9 (Yubico-proprietary)
540/// - Touch/PIN policy configuration: Yubico-proprietary extensions
541///   to the GENERATE ASYMMETRIC KEY PAIR command
542pub trait YubikeyBackend: PivBackend {
543    /// Generate an attestation certificate for a key in `slot`.
544    ///
545    /// Proves the key was generated on-device (not imported).
546    /// Uses `YubiKey`'s attestation key (slot F9) to sign a certificate
547    /// over the public key in the target slot.
548    /// This is a Yubico extension, not available on generic PIV cards.
549    fn attest_slot(&self, slot: PivSlot) -> Result<Vec<u8>, BackendError>;
550
551    /// Authenticate with the management key (3DES or AES).
552    ///
553    /// Required before administrative operations: key generation,
554    /// certificate import, PIN/PUK retry configuration.
555    fn authenticate_management(&mut self, mgm_key: &[u8]) -> Result<(), BackendError>;
556
557    /// Change the management key.
558    fn change_management_key(&mut self, current: &[u8], new: &[u8]) -> Result<(), BackendError>;
559
560    /// Get `YubiKey`-specific metadata for `slot`.
561    ///
562    /// Returns touch policy, PIN policy, and key origin: information
563    /// not available through standard PIV commands.
564    fn slot_metadata(&self, slot: PivSlot) -> Result<YubikeySlotMetadata, BackendError>;
565
566    /// Block PUK permanently (prevents PIN unblock forever).
567    ///
568    /// High-security operation: once blocked, a forgotten PIN requires
569    /// full device reset (destroying all keys). Irreversible.
570    fn block_puk(&mut self) -> Result<(), BackendError>;
571}
572
573/// Whether an error may be re-executed by retrying the step.
574///
575/// A `Transient` error means the world wasn't ready and the step's work did not
576/// happen, so re-running it is meaningful (token reinserted, cable reseated). A
577/// `Fatal` error means re-execution is either meaningless or unsafe: a procedural
578/// result, a broken definition, or a terminal device state.
579///
580/// The classification lives on the error type, not in the ceremony definition: a
581/// ceremony author must not be able to mark, say, a signature mismatch retriable.
582#[non_exhaustive]
583#[derive(Debug, Clone, Copy, PartialEq, Eq)]
584pub enum Retriability {
585    /// Environmental and recoverable; retrying the step may succeed.
586    Transient,
587    /// Not retriable; the run must stop or escalate.
588    Fatal,
589}
590
591impl Retriability {
592    /// Convenience predicate for callers that only need a yes/no answer.
593    #[must_use]
594    pub const fn is_retriable(self) -> bool {
595        matches!(self, Retriability::Transient)
596    }
597}
598
599/// Backend-specific errors.
600#[derive(Debug, thiserror::Error)]
601#[non_exhaustive]
602pub enum BackendError {
603    /// Authentication required (not logged in).
604    #[error("Authentication required")]
605    AuthRequired,
606
607    /// PIN required to access backend.
608    #[error("PIN required")]
609    PinRequired,
610
611    /// PIN verification failed (wrong PIN). Contains remaining retries if known.
612    #[error("PIN verification failed ({0} retries remaining)")]
613    PinFailed(u32),
614
615    /// PIN is blocked (too many failed attempts).
616    #[error("PIN blocked")]
617    PinBlocked,
618
619    /// User PIN not initialized (PKCS#11: `C_InitPIN` not yet called).
620    #[error("User PIN not initialized")]
621    UserPinNotInitialized,
622
623    /// Management key authentication required.
624    #[error("Management key authentication required")]
625    ManagementKeyRequired,
626
627    /// Key not found in backend storage.
628    #[error("Key not found: {0}")]
629    KeyNotFound(String),
630
631    /// Object not found (PKCS#11: `find_objects` returned empty).
632    #[error("Object not found: {0}")]
633    ObjectNotFound(String),
634
635    /// PIV slot is empty (no key or certificate).
636    #[error("Slot empty: {0}")]
637    SlotEmpty(String),
638
639    /// Backend storage capacity exceeded.
640    #[error("Capacity exceeded")]
641    CapacityExceeded,
642
643    /// Operation not permitted (key usage flags don't allow this).
644    #[error("Operation not permitted: {0}")]
645    OperationNotPermitted(String),
646
647    /// Attribute is read-only (e.g., tried to set `CKA_ALWAYS_SENSITIVE`).
648    #[error("Attribute is read-only: {0}")]
649    AttributeReadOnly(String),
650
651    /// Algorithm not supported by this backend.
652    #[error("Unsupported algorithm: {0}")]
653    UnsupportedAlgorithm(String),
654
655    /// Operation not supported by this backend.
656    #[error("Unsupported operation: {0}")]
657    UnsupportedOperation(String),
658
659    /// Token not present in slot.
660    #[error("Token not present")]
661    TokenNotPresent,
662
663    /// Hardware failure (device disconnected, I/O error, etc.).
664    #[error("Hardware failure: {0}")]
665    HardwareFailure(String),
666
667    /// An earlier operation on the device was interrupted, so its state
668    /// (verified PIN, authenticated management key, open command) is unknown.
669    #[error("Device state unknown: {0}")]
670    DeviceStateUnknown(String),
671
672    /// Invalid key format or data.
673    #[error("Invalid key format: {0}")]
674    InvalidKeyFormat(String),
675
676    /// Invalid data (malformed input, unexpected encoding, etc.).
677    #[error("Invalid data: {0}")]
678    InvalidData(String),
679
680    /// Backend not found in registry.
681    #[error("Backend not found: {0}")]
682    NotFound(String),
683
684    /// Configuration error (missing or invalid settings).
685    #[error("Configuration error: {0}")]
686    Configuration(String),
687
688    /// Generic backend error.
689    #[error("Backend error: {0}")]
690    Other(String),
691}
692
693impl BackendError {
694    /// Classify whether a step that failed with this error may be retried.
695    ///
696    /// The match is exhaustive with no wildcard arm on purpose: adding a new
697    /// `BackendError` variant fails to compile until it is deliberately
698    /// classified here.
699    #[must_use]
700    pub fn retriability(&self) -> Retriability {
701        match self {
702            // Environmental: the world wasn't ready, the work didn't happen.
703            // The operator can fix the precondition (log in, enter a PIN,
704            // insert the right token, initialize the device) and retry.
705            BackendError::AuthRequired
706            | BackendError::PinRequired
707            | BackendError::PinFailed(_)
708            | BackendError::ManagementKeyRequired
709            | BackendError::UserPinNotInitialized
710            | BackendError::KeyNotFound(_)
711            | BackendError::ObjectNotFound(_)
712            | BackendError::SlotEmpty(_)
713            | BackendError::TokenNotPresent
714            | BackendError::HardwareFailure(_)
715            | BackendError::Configuration(_) => Retriability::Transient,
716
717            // Fatal: re-execution is meaningless or unsafe. A terminal device
718            // state (PIN budget exhausted, capacity full, state unknown), a
719            // definition that asks for something the backend cannot do, or
720            // malformed/unknown input that retrying cannot resolve.
721            BackendError::PinBlocked
722            | BackendError::CapacityExceeded
723            | BackendError::DeviceStateUnknown(_)
724            | BackendError::OperationNotPermitted(_)
725            | BackendError::AttributeReadOnly(_)
726            | BackendError::UnsupportedAlgorithm(_)
727            | BackendError::UnsupportedOperation(_)
728            | BackendError::InvalidKeyFormat(_)
729            | BackendError::InvalidData(_)
730            | BackendError::NotFound(_)
731            | BackendError::Other(_) => Retriability::Fatal,
732        }
733    }
734}
735
736/// Lock the handle of a device that is not `Sync`.
737///
738/// `Backend` is `Send + Sync`, and a hardware handle (a PC/SC card, a PKCS#11
739/// session) is not, so a hardware backend keeps it behind a `Mutex`. The lock
740/// is poisoned when an operation panics while holding it, which leaves the
741/// device in an unknown state; the backend then refuses further use rather
742/// than carry that state into the next step.
743///
744/// # Errors
745///
746/// Returns [`BackendError::DeviceStateUnknown`] if the lock is poisoned.
747pub fn lock_device<T>(device: &Mutex<T>) -> Result<MutexGuard<'_, T>, BackendError> {
748    device.lock().map_err(|_| {
749        BackendError::DeviceStateUnknown(
750            "an earlier operation was interrupted; the run cannot continue on this device"
751                .to_string(),
752        )
753    })
754}
755
756#[cfg(test)]
757mod tests {
758    use super::*;
759
760    #[test]
761    fn retriability_is_retriable_predicate() {
762        assert!(Retriability::Transient.is_retriable());
763        assert!(!Retriability::Fatal.is_retriable());
764    }
765
766    #[test]
767    fn environmental_errors_are_transient() {
768        let transient = [
769            BackendError::AuthRequired,
770            BackendError::PinRequired,
771            BackendError::PinFailed(2),
772            BackendError::ManagementKeyRequired,
773            BackendError::UserPinNotInitialized,
774            BackendError::KeyNotFound("k".into()),
775            BackendError::ObjectNotFound("o".into()),
776            BackendError::SlotEmpty("9a".into()),
777            BackendError::TokenNotPresent,
778            BackendError::HardwareFailure("cable".into()),
779            BackendError::Configuration("missing".into()),
780        ];
781        for err in transient {
782            assert_eq!(
783                err.retriability(),
784                Retriability::Transient,
785                "expected transient: {err:?}"
786            );
787        }
788    }
789
790    #[test]
791    fn fatal_errors_are_not_retriable() {
792        let fatal = [
793            BackendError::PinBlocked,
794            BackendError::CapacityExceeded,
795            BackendError::DeviceStateUnknown("interrupted".into()),
796            BackendError::OperationNotPermitted("usage".into()),
797            BackendError::AttributeReadOnly("attr".into()),
798            BackendError::UnsupportedAlgorithm("rsa1".into()),
799            BackendError::UnsupportedOperation("wrap".into()),
800            BackendError::InvalidKeyFormat("pem".into()),
801            BackendError::InvalidData("bytes".into()),
802            BackendError::NotFound("backend".into()),
803            BackendError::Other("boom".into()),
804        ];
805        for err in fatal {
806            assert_eq!(
807                err.retriability(),
808                Retriability::Fatal,
809                "expected fatal: {err:?}"
810            );
811        }
812    }
813
814    #[test]
815    fn pin_failed_retriable_but_pin_blocked_terminal() {
816        // The hardware budget: a wrong PIN can be retried; an exhausted one cannot.
817        assert!(BackendError::PinFailed(1).retriability().is_retriable());
818        assert!(!BackendError::PinBlocked.retriability().is_retriable());
819    }
820
821    #[test]
822    fn a_poisoned_device_lock_is_a_fatal_error() {
823        let device = Mutex::new(0u8);
824        let poisoned = std::thread::scope(|s| {
825            s.spawn(|| {
826                let _guard = device.lock().unwrap();
827                panic!("operation interrupted while holding the device");
828            })
829            .join()
830        });
831        assert!(poisoned.is_err(), "the holder must have panicked");
832
833        let err = lock_device(&device).unwrap_err();
834        assert!(
835            matches!(err, BackendError::DeviceStateUnknown(_)),
836            "a poisoned lock must not hand out the device: {err:?}"
837        );
838        assert_eq!(err.retriability(), Retriability::Fatal);
839    }
840
841    #[test]
842    fn an_unpoisoned_device_lock_hands_out_the_device() {
843        let device = Mutex::new(7u8);
844        assert_eq!(*lock_device(&device).unwrap(), 7);
845    }
846}