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