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