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