Skip to main content

rite_sdk/
types.rs

1//! Data types shared between the runtime and backend implementations.
2//!
3//! These types form the vocabulary of the backend interface: key specifications,
4//! algorithm identifiers, attestation evidence, and hardware-specific metadata.
5
6use serde::{Deserialize, Serialize};
7use std::fmt;
8
9/// Opaque key identifier (backend-specific).
10///
11/// This is an opaque reference to a key managed by a backend. The internal
12/// format is backend-specific (e.g., `"slot_9c"` for `YubiKey` PIV, UUID for
13/// software backend).
14///
15/// Ceremony DSL never interprets the contents of a `KeyId`; it is purely a
16/// reference that gets passed back to the backend.
17#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
18#[serde(transparent)]
19pub struct KeyId(String);
20
21impl KeyId {
22    /// Create a new `KeyId` from a string.
23    pub fn new(s: impl Into<String>) -> Self {
24        Self(s.into())
25    }
26
27    /// Return the `KeyId` as a string slice.
28    pub fn as_str(&self) -> &str {
29        &self.0
30    }
31}
32
33impl AsRef<str> for KeyId {
34    fn as_ref(&self) -> &str {
35        &self.0
36    }
37}
38
39impl fmt::Display for KeyId {
40    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
41        write!(f, "{}", self.0)
42    }
43}
44
45impl From<String> for KeyId {
46    fn from(s: String) -> Self {
47        Self(s)
48    }
49}
50
51impl From<&str> for KeyId {
52    fn from(s: &str) -> Self {
53        Self(s.to_string())
54    }
55}
56
57/// Metadata returned after key generation or import.
58#[derive(Debug, Clone, Serialize, Deserialize)]
59pub struct KeyMetadata {
60    /// Opaque backend-specific key identifier.
61    pub key_id: KeyId,
62    /// Key algorithm.
63    pub algorithm: KeyAlgorithm,
64    /// Human-readable label.
65    pub label: String,
66    /// Public key in SPKI DER format (if exportable).
67    pub public_key: Option<Vec<u8>>,
68    /// Attestation evidence (if backend supports attestation).
69    pub attestation: Option<Attestation>,
70}
71
72/// Key algorithm.
73#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
74#[serde(into = "String", try_from = "String")]
75#[non_exhaustive]
76pub enum KeyAlgorithm {
77    /// RSA 2048-bit key.
78    Rsa2048,
79    /// RSA 4096-bit key.
80    Rsa4096,
81    /// ECDSA with P-256 curve (secp256r1).
82    EcdsaP256,
83    /// ECDSA with P-384 curve (secp384r1).
84    EcdsaP384,
85    /// Ed25519 (`EdDSA`).
86    Ed25519,
87    /// AES 128-bit symmetric key.
88    Aes128,
89    /// AES 256-bit symmetric key.
90    Aes256,
91}
92
93impl fmt::Display for KeyAlgorithm {
94    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
95        match self {
96            KeyAlgorithm::Rsa2048 => write!(f, "RSA-2048"),
97            KeyAlgorithm::Rsa4096 => write!(f, "RSA-4096"),
98            KeyAlgorithm::EcdsaP256 => write!(f, "ECDSA-P256"),
99            KeyAlgorithm::EcdsaP384 => write!(f, "ECDSA-P384"),
100            KeyAlgorithm::Ed25519 => write!(f, "Ed25519"),
101            KeyAlgorithm::Aes128 => write!(f, "AES-128"),
102            KeyAlgorithm::Aes256 => write!(f, "AES-256"),
103        }
104    }
105}
106
107/// Error returned when parsing an SDK algorithm identifier from a string fails.
108///
109/// Shared across all algorithm enums in this crate.
110#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
111#[error("unknown value: {0:?}")]
112pub struct ParseError(String);
113
114impl std::str::FromStr for KeyAlgorithm {
115    type Err = ParseError;
116
117    fn from_str(s: &str) -> Result<Self, Self::Err> {
118        match s {
119            "RSA-2048" => Ok(Self::Rsa2048),
120            "RSA-4096" => Ok(Self::Rsa4096),
121            "ECDSA-P256" => Ok(Self::EcdsaP256),
122            "ECDSA-P384" => Ok(Self::EcdsaP384),
123            "Ed25519" => Ok(Self::Ed25519),
124            "AES-128" => Ok(Self::Aes128),
125            "AES-256" => Ok(Self::Aes256),
126            _ => Err(ParseError(s.to_owned())),
127        }
128    }
129}
130
131impl From<KeyAlgorithm> for String {
132    fn from(a: KeyAlgorithm) -> String {
133        a.to_string()
134    }
135}
136
137impl TryFrom<String> for KeyAlgorithm {
138    type Error = ParseError;
139
140    fn try_from(s: String) -> Result<Self, Self::Error> {
141        s.parse()
142    }
143}
144
145bitflags::bitflags! {
146    /// Key usage flags. Maps to PKCS#11 `CKA_SIGN`, `CKA_VERIFY`, etc.
147    ///
148    /// PIV ignores these (slot determines usage). PKCS#11 requires them at creation.
149    /// Using bitflags rather than 7 bools: more compact for serialization, makes
150    /// set operations natural, and avoids nonsensical bool combinations.
151    #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
152    pub struct KeyUsages: u8 {
153        /// Key may be used to sign data.
154        const SIGN    = 0b0000_0001;
155        /// Key may be used to verify signatures.
156        const VERIFY  = 0b0000_0010;
157        /// Key may be used to encrypt data.
158        const ENCRYPT = 0b0000_0100;
159        /// Key may be used to decrypt data.
160        const DECRYPT = 0b0000_1000;
161        /// Key may be used to wrap other keys.
162        const WRAP    = 0b0001_0000;
163        /// Key may be used to unwrap other keys.
164        const UNWRAP  = 0b0010_0000;
165        /// Key may be used to derive other keys.
166        const DERIVE  = 0b0100_0000;
167    }
168}
169
170/// Security and usage policy for a generated or imported key.
171///
172/// Backends that cannot honour a requested policy MUST return
173/// `BackendError::OperationNotPermitted` rather than silently ignoring it.
174/// For example, a PIV backend receiving `extractable: true` must reject it
175/// because PIV keys are always non-extractable by hardware design.
176// Each bool maps directly to a named PKCS#11 boolean attribute (CKA_TOKEN,
177// CKA_SENSITIVE, CKA_EXTRACTABLE, CKA_WRAP_WITH_TRUSTED). Converting these
178// to two-variant enums would add type noise with no semantic benefit.
179#[allow(clippy::struct_excessive_bools)]
180#[derive(Debug, Clone, Serialize, Deserialize)]
181pub struct KeyPolicy {
182    /// Key persists after session ends. Always `true` for PIV. `CKA_TOKEN` in PKCS#11.
183    pub persistent: bool,
184    /// Key material never revealed in plaintext. Always `true` for PIV. `CKA_SENSITIVE`.
185    pub sensitive: bool,
186    /// Key can be wrapped and exported. Always `false` for PIV. `CKA_EXTRACTABLE`.
187    pub extractable: bool,
188    /// Key can only be wrapped by a `CKA_TRUSTED` wrapping key. PKCS#11 `CKA_WRAP_WITH_TRUSTED`.
189    pub wrap_with_trusted_only: bool,
190    /// What operations this key is permitted to perform.
191    pub usages: KeyUsages,
192}
193
194impl Default for KeyPolicy {
195    /// Most secure configuration for ceremony signing keys:
196    /// persistent=true, sensitive=true, extractable=false, sign+verify only.
197    fn default() -> Self {
198        Self {
199            persistent: true,
200            sensitive: true,
201            extractable: false,
202            wrap_with_trusted_only: false,
203            usages: KeyUsages::SIGN | KeyUsages::VERIFY,
204        }
205    }
206}
207
208/// Full specification for key generation or import.
209///
210/// Replaces the `(algorithm, label, slot_hint)` triplet with a structured type
211/// that can carry PKCS#11 policy attributes.
212#[derive(Debug, Clone, Serialize, Deserialize)]
213pub struct KeySpec {
214    /// Key algorithm.
215    pub algorithm: KeyAlgorithm,
216    /// Human-readable label.
217    pub label: String,
218    /// Security and usage policy.
219    pub policy: KeyPolicy,
220    /// Backend-specific location hint.
221    /// PIV: "9c" maps to Signature slot. PKCS#11: slot index or partition label.
222    /// Software: ignored.
223    pub location_hint: Option<String>,
224}
225
226/// PKCS#11 key security attributes read from an HSM after key generation.
227///
228/// Records HSM-managed attribute state as evidence of key provenance and
229/// policy compliance in the ceremony transcript.
230// Each bool maps directly to a named PKCS#11 boolean attribute (CKA_ALWAYS_SENSITIVE,
231// CKA_NEVER_EXTRACTABLE, CKA_SENSITIVE, CKA_EXTRACTABLE, CKA_WRAP_WITH_TRUSTED).
232// These are distinct concepts; collapsing them into enums would obscure the PKCS#11 semantics.
233#[allow(clippy::struct_excessive_bools)]
234#[derive(Debug, Clone, Serialize, Deserialize)]
235pub struct KeySecurityAttributes {
236    /// Key was ALWAYS sensitive: never had `CKA_SENSITIVE=false` since creation.
237    /// This is the primary provenance indicator for PKCS#11 keys.
238    pub always_sensitive: bool,
239    /// Key was NEVER extractable: never had `CKA_EXTRACTABLE=true` since creation.
240    pub never_extractable: bool,
241    /// Current value of `CKA_SENSITIVE`.
242    pub sensitive: bool,
243    /// Current value of `CKA_EXTRACTABLE`.
244    pub extractable: bool,
245    /// Key can only be wrapped by a key with `CKA_TRUSTED=true` (`CKA_WRAP_WITH_TRUSTED`).
246    pub wrap_with_trusted_only: bool,
247    /// What operations this key is permitted to perform (read from key object).
248    pub usages: KeyUsages,
249}
250
251/// Signature algorithm.
252#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
253#[serde(rename_all = "snake_case")]
254#[non_exhaustive]
255pub enum SignAlgorithm {
256    /// RSASSA-PKCS1-v1_5 with SHA-256.
257    RsaPkcs1Sha256,
258    /// RSASSA-PSS with SHA-256.
259    RsaPssSha256,
260    /// ECDSA with SHA-256.
261    EcdsaSha256,
262    /// ECDSA with SHA-384.
263    EcdsaSha384,
264    /// Ed25519 (pure `EdDSA`, no hash function).
265    Ed25519,
266}
267
268impl SignAlgorithm {
269    /// The key algorithm this signature algorithm is used with.
270    ///
271    /// This pairing is the shared source of truth for backends that select a
272    /// device algorithm from a signature request and for test doubles that
273    /// mint stand-in keys, so the two cannot drift apart.
274    #[must_use]
275    pub fn key_algorithm(self) -> KeyAlgorithm {
276        match self {
277            SignAlgorithm::RsaPkcs1Sha256 | SignAlgorithm::RsaPssSha256 => KeyAlgorithm::Rsa2048,
278            SignAlgorithm::EcdsaSha256 => KeyAlgorithm::EcdsaP256,
279            SignAlgorithm::EcdsaSha384 => KeyAlgorithm::EcdsaP384,
280            SignAlgorithm::Ed25519 => KeyAlgorithm::Ed25519,
281        }
282    }
283}
284
285/// Wrapping algorithm. Determines both the cryptographic method and the output format.
286#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
287#[serde(into = "String", try_from = "String")]
288#[non_exhaustive]
289pub enum WrapAlgorithm {
290    /// CMS `EnvelopedData` with RSA PKCS#1 v1.5 + AES-256-CBC (legacy).
291    /// Output: CMS `ContentInfo` DER.
292    CmsRsaCbc,
293    /// CMS `AuthEnvelopedData` with RSA PKCS#1 v1.5 + AES-256-GCM (recommended).
294    /// Output: CMS `ContentInfo` DER.
295    CmsRsaGcm,
296    /// NIST AES Key Wrap (RFC 3394). Requires a symmetric wrapping key.
297    /// Output: raw wrapped key bytes (8-byte aligned).
298    AesKeyWrap,
299    /// NIST AES Key Wrap with Padding (RFC 5649). Requires a symmetric wrapping key.
300    /// Output: raw wrapped key bytes (arbitrary length input).
301    AesKeyWrapPad,
302    /// RSA-OAEP with SHA-256. Output: raw RSA-OAEP encrypted key bytes.
303    RsaOaepSha256,
304}
305
306impl fmt::Display for WrapAlgorithm {
307    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
308        match self {
309            WrapAlgorithm::CmsRsaCbc => write!(f, "CMS-RSA-CBC"),
310            WrapAlgorithm::CmsRsaGcm => write!(f, "CMS-RSA-GCM"),
311            WrapAlgorithm::AesKeyWrap => write!(f, "AES-KW"),
312            WrapAlgorithm::AesKeyWrapPad => write!(f, "AES-KWP"),
313            WrapAlgorithm::RsaOaepSha256 => write!(f, "RSA-OAEP-SHA256"),
314        }
315    }
316}
317
318impl std::str::FromStr for WrapAlgorithm {
319    type Err = ParseError;
320
321    fn from_str(s: &str) -> Result<Self, Self::Err> {
322        match s {
323            "CMS-RSA-CBC" => Ok(Self::CmsRsaCbc),
324            "CMS-RSA-GCM" => Ok(Self::CmsRsaGcm),
325            "AES-KW" => Ok(Self::AesKeyWrap),
326            "AES-KWP" => Ok(Self::AesKeyWrapPad),
327            "RSA-OAEP-SHA256" => Ok(Self::RsaOaepSha256),
328            _ => Err(ParseError(s.to_owned())),
329        }
330    }
331}
332
333impl From<WrapAlgorithm> for String {
334    fn from(a: WrapAlgorithm) -> String {
335        a.to_string()
336    }
337}
338
339impl TryFrom<String> for WrapAlgorithm {
340    type Error = ParseError;
341
342    fn try_from(s: String) -> Result<Self, Self::Error> {
343        s.parse()
344    }
345}
346
347/// Output format family, derived from [`WrapAlgorithm`].
348///
349/// Used for display, artifact metadata, and compatibility checking.
350#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
351#[serde(rename_all = "snake_case")]
352#[non_exhaustive]
353pub enum WrappedKeyFormat {
354    /// CMS `ContentInfo` DER.
355    Cms,
356    /// Raw AES Key Wrap bytes (RFC 3394 / RFC 5649).
357    AesKeyWrap,
358    /// Raw RSA-OAEP encrypted bytes.
359    RsaOaep,
360}
361
362/// A wrapped (encrypted) key with its algorithm and metadata.
363#[derive(Debug, Clone, Serialize, Deserialize)]
364pub struct WrappedKey {
365    /// Algorithm used to wrap the key.
366    pub algorithm: WrapAlgorithm,
367    /// Wrapped key data (format depends on algorithm).
368    pub data: Vec<u8>,
369    /// Human-readable hint identifying the intended unwrapping key (for audit).
370    pub recipient_hint: Option<String>,
371}
372
373impl WrappedKey {
374    /// Return the output format family for this wrapped key.
375    pub fn format(&self) -> WrappedKeyFormat {
376        match self.algorithm {
377            WrapAlgorithm::CmsRsaCbc | WrapAlgorithm::CmsRsaGcm => WrappedKeyFormat::Cms,
378            WrapAlgorithm::AesKeyWrap | WrapAlgorithm::AesKeyWrapPad => {
379                WrappedKeyFormat::AesKeyWrap
380            }
381            WrapAlgorithm::RsaOaepSha256 => WrappedKeyFormat::RsaOaep,
382        }
383    }
384}
385
386/// The kind of attestation evidence a backend can produce.
387#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
388#[serde(rename_all = "snake_case")]
389#[non_exhaustive]
390pub enum AttestationKind {
391    /// Vendor cert chain proving on-device generation. Independently verifiable
392    /// against a public manufacturer root CA.
393    /// Examples: `YubiKey` F9 attestation, Thales Luna PKC (Public Key Confirmation).
394    HardwareCertChain,
395    /// TPM signed quote over PCR values with nonce. Proves platform state.
396    TpmQuote,
397    /// PKCS#11 attribute flags: `CKA_ALWAYS_SENSITIVE=true` + `CKA_NEVER_EXTRACTABLE=true`.
398    /// Trustworthy within the HSM's trust boundary but not independently
399    /// cryptographically verifiable.
400    Pkcs11Attributes,
401}
402
403/// Structured attestation evidence with a discriminant kind.
404///
405/// The `kind` field tells consumers what they're looking at and how to verify it.
406#[derive(Debug, Clone, Serialize, Deserialize)]
407pub struct Attestation {
408    /// What kind of attestation this is.
409    pub kind: AttestationKind,
410    /// DER-encoded certificate chain. Leaf first, then intermediates, then root (if known).
411    /// Empty for `Pkcs11Attributes` kind.
412    pub certificates: Vec<Vec<u8>>,
413    /// Raw signature bytes (if applicable). None for attribute-based attestation.
414    pub signature: Option<Vec<u8>>,
415    /// Additional metadata (key attributes, slot info, etc.) for transcript recording.
416    pub metadata: serde_json::Value,
417}
418
419/// Device information for platform attestation.
420///
421/// Collected from the system running the ceremony. Machine IDs are hashed
422/// (SHA-256) for privacy before inclusion in evidence.
423#[derive(Debug, Clone, Serialize, Deserialize)]
424pub struct DeviceInfo {
425    /// System hostname.
426    pub hostname: String,
427    /// Hashed machine ID (SHA-256, format: "sha256:hexhash").
428    /// None if machine ID not available on this platform.
429    pub machine_id: Option<String>,
430    /// CPU model string.
431    pub cpu_model: Option<String>,
432    /// Operating system name.
433    pub os_name: Option<String>,
434    /// Operating system version.
435    pub os_version: Option<String>,
436    /// Kernel version.
437    pub kernel_version: Option<String>,
438}
439
440/// PIV key slot identifiers.
441///
442/// Defined by NIST SP 800-73-5 Part 1, Table 4b ("PIV Card Application Card
443/// Command Interface: Key References"). These slot assignments are fixed by
444/// the standard and MUST NOT be changed.
445///
446/// # Standard references
447/// - NIST SP 800-73-5 Part 1, §3.1.2: Key References
448/// - NIST SP 800-73-5 Part 2, §3.2: GENERAL AUTHENTICATE
449#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
450pub enum PivSlot {
451    /// Slot 9A: PIV Authentication Key.
452    /// Used for card/cardholder authentication (e.g. system login).
453    /// PIN required once per session.
454    /// [NIST SP 800-73-5 Part 1, §3.1.2]
455    Authentication,
456
457    /// Slot 9C: Digital Signature Key.
458    /// Used for document/code signing. PIN required before EVERY
459    /// private key operation ("PIN Always" access rule).
460    /// Data to be signed is hashed off-card.
461    /// [NIST SP 800-73-5 Part 1, §3.1.2; Part 2, §3.2.4]
462    Signature,
463
464    /// Slot 9D: Key Management Key.
465    /// Used for key establishment (encryption/decryption).
466    /// PIN required once per session.
467    /// [NIST SP 800-73-5 Part 1, §3.1.2]
468    KeyManagement,
469
470    /// Slot 9E: Card Authentication Key.
471    /// Used for physical access (e.g. PIV-enabled door locks).
472    /// NO PIN required for private key operations.
473    /// [NIST SP 800-73-5 Part 1, §3.1.2]
474    CardAuthentication,
475
476    /// Retired Key Management Key slot.
477    ///
478    /// Holds a previously used Key Management key for decrypting historical
479    /// documents. The inner value is an index in `0..=19`, where 0 corresponds
480    /// to PIV key reference 0x82 and 19 corresponds to 0x95.
481    ///
482    /// Construct with [`PivSlot::retired`] to enforce the valid range.
483    /// [NIST SP 800-73-5 Part 1, §3.1.2]
484    Retired(u8),
485}
486
487impl PivSlot {
488    /// Create a retired key management slot from an index in `0..=19`.
489    ///
490    /// Returns `None` if `index` is out of the valid range.
491    /// The 20 retired slots correspond to PIV key references 0x82–0x95.
492    pub fn retired(index: u8) -> Option<Self> {
493        (index <= 19).then_some(Self::Retired(index))
494    }
495}
496
497/// Unified certificate addressing across backend types.
498///
499/// PIV addresses certs by slot. PKCS#11 addresses cert objects by `CKA_LABEL` or `CKA_ID`.
500/// Both are "find me the cert for this key"; the addressing differs by backend type.
501#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
502#[non_exhaustive]
503pub enum CertRef {
504    /// PIV slot identifier (`PivBackend` and `YubikeyBackend`).
505    PivSlot(PivSlot),
506    /// Human-readable label string (PKCS#11 `CKA_LABEL`, or software backend).
507    Label(String),
508    /// Raw binary identifier (PKCS#11 `CKA_ID`, typically SHA-1 of public key).
509    RawId(Vec<u8>),
510}
511
512/// PIN access policy for PIV private key operations.
513///
514/// Standard PIV defines fixed PIN policies per slot (see `PivSlot` docs).
515/// `YubiKey` extends this with per-key configurable policies.
516///
517/// # Standard references
518/// - NIST SP 800-73-5 Part 2, §3.2: access rules per key reference
519/// - Yubico PIV documentation: configurable PIN policies (vendor extension)
520#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
521#[serde(rename_all = "snake_case")]
522#[non_exhaustive]
523pub enum PivPinPolicy {
524    /// Use the slot's default policy per NIST SP 800-73.
525    Default,
526    /// No PIN required (vendor extension, not standard PIV).
527    Never,
528    /// PIN verified once per session.
529    Once,
530    /// PIN verified before every private key operation ("PIN Always").
531    Always,
532}
533
534/// Physical touch policy for private key operations.
535///
536/// This is a vendor extension (Yubico), not part of the NIST PIV standard.
537/// Standard PIV cards do not have a touch sensor concept.
538#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
539#[serde(rename_all = "snake_case")]
540#[non_exhaustive]
541pub enum PivTouchPolicy {
542    /// Use the slot's default touch policy.
543    Default,
544    /// No touch required.
545    Never,
546    /// Touch required for every operation.
547    Always,
548    /// Touch cached for 15 seconds (Yubico-specific).
549    Cached,
550}
551
552/// Metadata about a populated PIV slot.
553#[derive(Debug, Clone, Serialize, Deserialize)]
554pub struct PivSlotInfo {
555    /// The PIV slot identifier.
556    pub slot: PivSlot,
557    /// Key algorithm in this slot (if known).
558    pub algorithm: Option<KeyAlgorithm>,
559    /// Whether a certificate is stored in this slot.
560    /// PIV associates one X.509 certificate with each key slot.
561    /// [NIST SP 800-73-5 Part 1, §3.2]
562    pub has_certificate: bool,
563    /// Origin of the key in this slot.
564    pub origin: PivKeyOrigin,
565}
566
567/// Origin of a key in a PIV slot.
568#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
569#[serde(rename_all = "snake_case")]
570#[non_exhaustive]
571pub enum PivKeyOrigin {
572    /// Key was generated on the device (GENERATE ASYMMETRIC KEY PAIR command).
573    /// [NIST SP 800-73-5 Part 2, §3.1]
574    Generated,
575    /// Key was imported from external material.
576    Imported,
577    /// Origin unknown (card doesn't report this information).
578    Unknown,
579}
580
581/// Device identity information for a PIV-compatible smart card.
582#[derive(Debug, Clone, Serialize, Deserialize)]
583pub struct PivDeviceInfo {
584    /// Device serial number (vendor-specific, not standardized by NIST).
585    pub serial: Option<String>,
586    /// Firmware version string.
587    pub firmware_version: Option<String>,
588    /// Form factor description (e.g. "USB-A", "USB-C", "NFC").
589    pub form_factor: Option<String>,
590}
591
592/// `YubiKey`-specific slot metadata (vendor extension, not NIST PIV).
593#[derive(Debug, Clone, Serialize, Deserialize)]
594pub struct YubikeySlotMetadata {
595    /// PIN policy configured for this slot.
596    pub pin_policy: PivPinPolicy,
597    /// Touch policy configured for this slot.
598    pub touch_policy: PivTouchPolicy,
599    /// Origin of the key in this slot.
600    pub origin: PivKeyOrigin,
601    /// Public key in `SubjectPublicKeyInfo` (SPKI) DER format.
602    pub public_key: Option<Vec<u8>>,
603}
604
605bitflags::bitflags! {
606    /// PKCS#11 token capability flags. Maps to the `CKF_*` constants in the PKCS#11 standard.
607    #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
608    pub struct Pkcs11TokenFlags: u8 {
609        /// Token requires login before cryptographic operations (`CKF_LOGIN_REQUIRED`).
610        const LOGIN_REQUIRED      = 0b0000_0001;
611        /// User PIN has been initialized: `C_InitPIN` has been called (`CKF_USER_PIN_INITIALIZED`).
612        const USER_PIN_INITIALIZED = 0b0000_0010;
613        /// Token has been initialized: `C_InitToken` has been called (`CKF_TOKEN_INITIALIZED`).
614        const TOKEN_INITIALIZED   = 0b0000_0100;
615        /// Token is write-protected (`CKF_WRITE_PROTECTED`).
616        const WRITE_PROTECTED     = 0b0000_1000;
617    }
618}
619
620/// PKCS#11 token information.
621#[derive(Debug, Clone, Serialize, Deserialize)]
622pub struct Pkcs11TokenInfo {
623    /// Token label (padded to 32 bytes in PKCS#11, trimmed here).
624    pub label: String,
625    /// Manufacturer ID.
626    pub manufacturer: String,
627    /// Token model.
628    pub model: String,
629    /// Token serial number.
630    pub serial: String,
631    /// Firmware version string.
632    pub firmware_version: String,
633    /// Token capability flags.
634    pub flags: Pkcs11TokenFlags,
635}
636
637/// Opaque PKCS#11 mechanism identifier.
638///
639/// An enum would be incomplete (hundreds of `CKM_*` values, plus vendor extensions).
640/// A string like `"CKM_RSA_PKCS_KEY_PAIR_GEN"` is honest about what we expose.
641/// Used only for capability checking ("does this token support X?"), not dispatch.
642#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
643#[serde(transparent)]
644pub struct Pkcs11Mechanism(String);
645
646impl Pkcs11Mechanism {
647    /// Create a new `Pkcs11Mechanism` from a `CKM_*` name string.
648    pub fn new(name: impl Into<String>) -> Self {
649        Self(name.into())
650    }
651
652    /// Return the mechanism name as a string slice.
653    pub fn as_str(&self) -> &str {
654        &self.0
655    }
656}
657
658impl fmt::Display for Pkcs11Mechanism {
659    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
660        write!(f, "{}", self.0)
661    }
662}
663
664/// TPM (Trusted Platform Module) information.
665#[derive(Debug, Clone, Serialize, Deserialize)]
666pub struct TpmInfo {
667    /// TPM specification version (e.g., "2.0").
668    pub version: String,
669    /// TPM manufacturer identifier.
670    pub manufacturer: String,
671    /// Firmware version (if available).
672    pub firmware_version: Option<String>,
673}
674
675/// PCR (Platform Configuration Register) value.
676///
677/// The `value` field encodes both the algorithm and the hash in the format
678/// `"algorithm:hexhash"` (e.g., `"sha256:abc123..."`).
679#[derive(Debug, Clone, Serialize, Deserialize)]
680pub struct PcrValue {
681    /// PCR index (0-23 typically).
682    pub index: u8,
683    /// PCR value in the format `"algorithm:hexhash"` (e.g., `"sha256:abc123..."`).
684    pub value: String,
685}
686
687/// Backend configuration entry from a ceremony file.
688///
689/// The `provider` field identifies the backend by its self-declared name
690/// (e.g., `"software"`, `"yubikey"`). The remaining YAML keys are flattened
691/// into `extra` for backend-specific deserialization.
692///
693/// Each backend crate defines its own config struct and deserializes from
694/// `extra` at startup. This inverts the prior design where the model listed
695/// concrete backend variants; backends now declare themselves.
696#[derive(Debug, Clone, Serialize, Deserialize)]
697pub struct BackendConfig {
698    /// Backend provider identifier as a lowercase string (e.g., `"software"`).
699    pub provider: String,
700    /// Backend-specific configuration key/value pairs.
701    #[serde(flatten)]
702    pub extra: serde_json::Value,
703}
704
705#[cfg(test)]
706mod tests {
707    use super::*;
708
709    #[test]
710    fn key_algorithm_serde_roundtrip() {
711        // Serde uses Display strings via `serde(into/try_from)`. These are the canonical
712        // strings for ceremony YAML `algorithm:` fields and transcripts.
713        let cases: &[(KeyAlgorithm, &str)] = &[
714            (KeyAlgorithm::Rsa2048, "\"RSA-2048\""),
715            (KeyAlgorithm::Rsa4096, "\"RSA-4096\""),
716            (KeyAlgorithm::EcdsaP256, "\"ECDSA-P256\""),
717            (KeyAlgorithm::EcdsaP384, "\"ECDSA-P384\""),
718            (KeyAlgorithm::Ed25519, "\"Ed25519\""),
719            (KeyAlgorithm::Aes128, "\"AES-128\""),
720            (KeyAlgorithm::Aes256, "\"AES-256\""),
721        ];
722        for &(variant, expected) in cases {
723            let serialized = serde_json::to_string(&variant).unwrap();
724            assert_eq!(serialized, expected, "serialize {variant:?}");
725            let deserialized: KeyAlgorithm = serde_json::from_str(expected).unwrap();
726            assert_eq!(deserialized, variant, "deserialize {expected}");
727        }
728    }
729
730    #[test]
731    fn wrap_algorithm_serde_roundtrip() {
732        // Serde uses Display strings via `serde(into/try_from)`. These strings appear
733        // in ceremony YAML `algorithm:` fields and transcripts.
734        let cases: &[(WrapAlgorithm, &str)] = &[
735            (WrapAlgorithm::CmsRsaCbc, "\"CMS-RSA-CBC\""),
736            (WrapAlgorithm::CmsRsaGcm, "\"CMS-RSA-GCM\""),
737            (WrapAlgorithm::AesKeyWrap, "\"AES-KW\""),
738            (WrapAlgorithm::AesKeyWrapPad, "\"AES-KWP\""),
739            (WrapAlgorithm::RsaOaepSha256, "\"RSA-OAEP-SHA256\""),
740        ];
741        for &(variant, expected) in cases {
742            let serialized = serde_json::to_string(&variant).unwrap();
743            assert_eq!(serialized, expected, "serialize {variant:?}");
744            let deserialized: WrapAlgorithm = serde_json::from_str(expected).unwrap();
745            assert_eq!(deserialized, variant, "deserialize {expected}");
746        }
747    }
748
749    #[test]
750    fn wrap_algorithm_from_str_rejects_unknown() {
751        assert!("CMS-RSA-XTS".parse::<WrapAlgorithm>().is_err());
752        assert!("".parse::<WrapAlgorithm>().is_err());
753        assert!(
754            "cms-rsa-gcm".parse::<WrapAlgorithm>().is_err(),
755            "must be case-sensitive"
756        );
757    }
758
759    #[test]
760    fn piv_slot_retired_enforces_valid_range() {
761        // Indices 0–19 map to PIV key references 0x82–0x95.
762        // An out-of-range index would send an invalid reference to PIV hardware.
763        assert!(PivSlot::retired(0).is_some(), "index 0 must be valid");
764        assert!(
765            PivSlot::retired(19).is_some(),
766            "index 19 must be valid (last slot 0x95)"
767        );
768        assert!(
769            PivSlot::retired(20).is_none(),
770            "index 20 must be rejected (out of range)"
771        );
772        assert!(
773            PivSlot::retired(255).is_none(),
774            "index 255 must be rejected"
775        );
776    }
777
778    #[test]
779    fn key_policy_default_is_secure() {
780        // The default is documented as the most secure configuration for ceremony signing keys.
781        // A regression here (e.g., extractable=true) is a silent security downgrade.
782        let policy = KeyPolicy::default();
783        assert!(
784            policy.persistent,
785            "ceremony keys must persist across sessions"
786        );
787        assert!(policy.sensitive, "ceremony keys must be marked sensitive");
788        assert!(
789            !policy.extractable,
790            "ceremony keys must not be extractable by default"
791        );
792        assert!(policy.usages.contains(KeyUsages::SIGN));
793        assert!(policy.usages.contains(KeyUsages::VERIFY));
794        assert!(
795            !policy.usages.contains(KeyUsages::ENCRYPT),
796            "signing-only default must not permit encryption"
797        );
798        assert!(
799            !policy.usages.contains(KeyUsages::WRAP),
800            "signing-only default must not permit wrapping"
801        );
802    }
803}