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
268/// Wrapping algorithm. Determines both the cryptographic method and the output format.
269#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
270#[serde(into = "String", try_from = "String")]
271#[non_exhaustive]
272pub enum WrapAlgorithm {
273    /// CMS `EnvelopedData` with RSA PKCS#1 v1.5 + AES-256-CBC (legacy).
274    /// Output: CMS `ContentInfo` DER.
275    CmsRsaCbc,
276    /// CMS `AuthEnvelopedData` with RSA PKCS#1 v1.5 + AES-256-GCM (recommended).
277    /// Output: CMS `ContentInfo` DER.
278    CmsRsaGcm,
279    /// NIST AES Key Wrap (RFC 3394). Requires a symmetric wrapping key.
280    /// Output: raw wrapped key bytes (8-byte aligned).
281    AesKeyWrap,
282    /// NIST AES Key Wrap with Padding (RFC 5649). Requires a symmetric wrapping key.
283    /// Output: raw wrapped key bytes (arbitrary length input).
284    AesKeyWrapPad,
285    /// RSA-OAEP with SHA-256. Output: raw RSA-OAEP encrypted key bytes.
286    RsaOaepSha256,
287}
288
289impl fmt::Display for WrapAlgorithm {
290    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
291        match self {
292            WrapAlgorithm::CmsRsaCbc => write!(f, "CMS-RSA-CBC"),
293            WrapAlgorithm::CmsRsaGcm => write!(f, "CMS-RSA-GCM"),
294            WrapAlgorithm::AesKeyWrap => write!(f, "AES-KW"),
295            WrapAlgorithm::AesKeyWrapPad => write!(f, "AES-KWP"),
296            WrapAlgorithm::RsaOaepSha256 => write!(f, "RSA-OAEP-SHA256"),
297        }
298    }
299}
300
301impl std::str::FromStr for WrapAlgorithm {
302    type Err = ParseError;
303
304    fn from_str(s: &str) -> Result<Self, Self::Err> {
305        match s {
306            "CMS-RSA-CBC" => Ok(Self::CmsRsaCbc),
307            "CMS-RSA-GCM" => Ok(Self::CmsRsaGcm),
308            "AES-KW" => Ok(Self::AesKeyWrap),
309            "AES-KWP" => Ok(Self::AesKeyWrapPad),
310            "RSA-OAEP-SHA256" => Ok(Self::RsaOaepSha256),
311            _ => Err(ParseError(s.to_owned())),
312        }
313    }
314}
315
316impl From<WrapAlgorithm> for String {
317    fn from(a: WrapAlgorithm) -> String {
318        a.to_string()
319    }
320}
321
322impl TryFrom<String> for WrapAlgorithm {
323    type Error = ParseError;
324
325    fn try_from(s: String) -> Result<Self, Self::Error> {
326        s.parse()
327    }
328}
329
330/// Output format family, derived from [`WrapAlgorithm`].
331///
332/// Used for display, artifact metadata, and compatibility checking.
333#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
334#[serde(rename_all = "snake_case")]
335#[non_exhaustive]
336pub enum WrappedKeyFormat {
337    /// CMS `ContentInfo` DER.
338    Cms,
339    /// Raw AES Key Wrap bytes (RFC 3394 / RFC 5649).
340    AesKeyWrap,
341    /// Raw RSA-OAEP encrypted bytes.
342    RsaOaep,
343}
344
345/// A wrapped (encrypted) key with its algorithm and metadata.
346#[derive(Debug, Clone, Serialize, Deserialize)]
347pub struct WrappedKey {
348    /// Algorithm used to wrap the key.
349    pub algorithm: WrapAlgorithm,
350    /// Wrapped key data (format depends on algorithm).
351    pub data: Vec<u8>,
352    /// Human-readable hint identifying the intended unwrapping key (for audit).
353    pub recipient_hint: Option<String>,
354}
355
356impl WrappedKey {
357    /// Return the output format family for this wrapped key.
358    pub fn format(&self) -> WrappedKeyFormat {
359        match self.algorithm {
360            WrapAlgorithm::CmsRsaCbc | WrapAlgorithm::CmsRsaGcm => WrappedKeyFormat::Cms,
361            WrapAlgorithm::AesKeyWrap | WrapAlgorithm::AesKeyWrapPad => {
362                WrappedKeyFormat::AesKeyWrap
363            }
364            WrapAlgorithm::RsaOaepSha256 => WrappedKeyFormat::RsaOaep,
365        }
366    }
367}
368
369/// The kind of attestation evidence a backend can produce.
370#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
371#[serde(rename_all = "snake_case")]
372#[non_exhaustive]
373pub enum AttestationKind {
374    /// Vendor cert chain proving on-device generation. Independently verifiable
375    /// against a public manufacturer root CA.
376    /// Examples: `YubiKey` F9 attestation, Thales Luna PKC (Public Key Confirmation).
377    HardwareCertChain,
378    /// TPM signed quote over PCR values with nonce. Proves platform state.
379    TpmQuote,
380    /// PKCS#11 attribute flags: `CKA_ALWAYS_SENSITIVE=true` + `CKA_NEVER_EXTRACTABLE=true`.
381    /// Trustworthy within the HSM's trust boundary but not independently
382    /// cryptographically verifiable.
383    Pkcs11Attributes,
384}
385
386/// Structured attestation evidence with a discriminant kind.
387///
388/// The `kind` field tells consumers what they're looking at and how to verify it.
389#[derive(Debug, Clone, Serialize, Deserialize)]
390pub struct Attestation {
391    /// What kind of attestation this is.
392    pub kind: AttestationKind,
393    /// DER-encoded certificate chain. Leaf first, then intermediates, then root (if known).
394    /// Empty for `Pkcs11Attributes` kind.
395    pub certificates: Vec<Vec<u8>>,
396    /// Raw signature bytes (if applicable). None for attribute-based attestation.
397    pub signature: Option<Vec<u8>>,
398    /// Additional metadata (key attributes, slot info, etc.) for transcript recording.
399    pub metadata: serde_json::Value,
400}
401
402/// Device information for platform attestation.
403///
404/// Collected from the system running the ceremony. Machine IDs are hashed
405/// (SHA-256) for privacy before inclusion in evidence.
406#[derive(Debug, Clone, Serialize, Deserialize)]
407pub struct DeviceInfo {
408    /// System hostname.
409    pub hostname: String,
410    /// Hashed machine ID (SHA-256, format: "sha256:hexhash").
411    /// None if machine ID not available on this platform.
412    pub machine_id: Option<String>,
413    /// CPU model string.
414    pub cpu_model: Option<String>,
415    /// Operating system name.
416    pub os_name: Option<String>,
417    /// Operating system version.
418    pub os_version: Option<String>,
419    /// Kernel version.
420    pub kernel_version: Option<String>,
421}
422
423/// PIV key slot identifiers.
424///
425/// Defined by NIST SP 800-73-5 Part 1, Table 4b ("PIV Card Application Card
426/// Command Interface: Key References"). These slot assignments are fixed by
427/// the standard and MUST NOT be changed.
428///
429/// # Standard references
430/// - NIST SP 800-73-5 Part 1, §3.1.2: Key References
431/// - NIST SP 800-73-5 Part 2, §3.2: GENERAL AUTHENTICATE
432#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
433pub enum PivSlot {
434    /// Slot 9A: PIV Authentication Key.
435    /// Used for card/cardholder authentication (e.g. system login).
436    /// PIN required once per session.
437    /// [NIST SP 800-73-5 Part 1, §3.1.2]
438    Authentication,
439
440    /// Slot 9C: Digital Signature Key.
441    /// Used for document/code signing. PIN required before EVERY
442    /// private key operation ("PIN Always" access rule).
443    /// Data to be signed is hashed off-card.
444    /// [NIST SP 800-73-5 Part 1, §3.1.2; Part 2, §3.2.4]
445    Signature,
446
447    /// Slot 9D: Key Management Key.
448    /// Used for key establishment (encryption/decryption).
449    /// PIN required once per session.
450    /// [NIST SP 800-73-5 Part 1, §3.1.2]
451    KeyManagement,
452
453    /// Slot 9E: Card Authentication Key.
454    /// Used for physical access (e.g. PIV-enabled door locks).
455    /// NO PIN required for private key operations.
456    /// [NIST SP 800-73-5 Part 1, §3.1.2]
457    CardAuthentication,
458
459    /// Retired Key Management Key slot.
460    ///
461    /// Holds a previously used Key Management key for decrypting historical
462    /// documents. The inner value is an index in `0..=19`, where 0 corresponds
463    /// to PIV key reference 0x82 and 19 corresponds to 0x95.
464    ///
465    /// Construct with [`PivSlot::retired`] to enforce the valid range.
466    /// [NIST SP 800-73-5 Part 1, §3.1.2]
467    Retired(u8),
468}
469
470impl PivSlot {
471    /// Create a retired key management slot from an index in `0..=19`.
472    ///
473    /// Returns `None` if `index` is out of the valid range.
474    /// The 20 retired slots correspond to PIV key references 0x82–0x95.
475    pub fn retired(index: u8) -> Option<Self> {
476        (index <= 19).then_some(Self::Retired(index))
477    }
478}
479
480/// Unified certificate addressing across backend types.
481///
482/// PIV addresses certs by slot. PKCS#11 addresses cert objects by `CKA_LABEL` or `CKA_ID`.
483/// Both are "find me the cert for this key"; the addressing differs by backend type.
484#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
485#[non_exhaustive]
486pub enum CertRef {
487    /// PIV slot identifier (`PivBackend` and `YubikeyBackend`).
488    PivSlot(PivSlot),
489    /// Human-readable label string (PKCS#11 `CKA_LABEL`, or software backend).
490    Label(String),
491    /// Raw binary identifier (PKCS#11 `CKA_ID`, typically SHA-1 of public key).
492    RawId(Vec<u8>),
493}
494
495/// PIN access policy for PIV private key operations.
496///
497/// Standard PIV defines fixed PIN policies per slot (see `PivSlot` docs).
498/// `YubiKey` extends this with per-key configurable policies.
499///
500/// # Standard references
501/// - NIST SP 800-73-5 Part 2, §3.2: access rules per key reference
502/// - Yubico PIV documentation: configurable PIN policies (vendor extension)
503#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
504#[serde(rename_all = "snake_case")]
505#[non_exhaustive]
506pub enum PivPinPolicy {
507    /// Use the slot's default policy per NIST SP 800-73.
508    Default,
509    /// No PIN required (vendor extension, not standard PIV).
510    Never,
511    /// PIN verified once per session.
512    Once,
513    /// PIN verified before every private key operation ("PIN Always").
514    Always,
515}
516
517/// Physical touch policy for private key operations.
518///
519/// This is a vendor extension (Yubico), not part of the NIST PIV standard.
520/// Standard PIV cards do not have a touch sensor concept.
521#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
522#[serde(rename_all = "snake_case")]
523#[non_exhaustive]
524pub enum PivTouchPolicy {
525    /// Use the slot's default touch policy.
526    Default,
527    /// No touch required.
528    Never,
529    /// Touch required for every operation.
530    Always,
531    /// Touch cached for 15 seconds (Yubico-specific).
532    Cached,
533}
534
535/// Metadata about a populated PIV slot.
536#[derive(Debug, Clone, Serialize, Deserialize)]
537pub struct PivSlotInfo {
538    /// The PIV slot identifier.
539    pub slot: PivSlot,
540    /// Key algorithm in this slot (if known).
541    pub algorithm: Option<KeyAlgorithm>,
542    /// Whether a certificate is stored in this slot.
543    /// PIV associates one X.509 certificate with each key slot.
544    /// [NIST SP 800-73-5 Part 1, §3.2]
545    pub has_certificate: bool,
546    /// Origin of the key in this slot.
547    pub origin: PivKeyOrigin,
548}
549
550/// Origin of a key in a PIV slot.
551#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
552#[serde(rename_all = "snake_case")]
553#[non_exhaustive]
554pub enum PivKeyOrigin {
555    /// Key was generated on the device (GENERATE ASYMMETRIC KEY PAIR command).
556    /// [NIST SP 800-73-5 Part 2, §3.1]
557    Generated,
558    /// Key was imported from external material.
559    Imported,
560    /// Origin unknown (card doesn't report this information).
561    Unknown,
562}
563
564/// Device identity information for a PIV-compatible smart card.
565#[derive(Debug, Clone, Serialize, Deserialize)]
566pub struct PivDeviceInfo {
567    /// Device serial number (vendor-specific, not standardized by NIST).
568    pub serial: Option<String>,
569    /// Firmware version string.
570    pub firmware_version: Option<String>,
571    /// Form factor description (e.g. "USB-A", "USB-C", "NFC").
572    pub form_factor: Option<String>,
573}
574
575/// `YubiKey`-specific slot metadata (vendor extension, not NIST PIV).
576#[derive(Debug, Clone, Serialize, Deserialize)]
577pub struct YubikeySlotMetadata {
578    /// PIN policy configured for this slot.
579    pub pin_policy: PivPinPolicy,
580    /// Touch policy configured for this slot.
581    pub touch_policy: PivTouchPolicy,
582    /// Origin of the key in this slot.
583    pub origin: PivKeyOrigin,
584    /// Public key in `SubjectPublicKeyInfo` (SPKI) DER format.
585    pub public_key: Option<Vec<u8>>,
586}
587
588bitflags::bitflags! {
589    /// PKCS#11 token capability flags. Maps to the `CKF_*` constants in the PKCS#11 standard.
590    #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
591    pub struct Pkcs11TokenFlags: u8 {
592        /// Token requires login before cryptographic operations (`CKF_LOGIN_REQUIRED`).
593        const LOGIN_REQUIRED      = 0b0000_0001;
594        /// User PIN has been initialized: `C_InitPIN` has been called (`CKF_USER_PIN_INITIALIZED`).
595        const USER_PIN_INITIALIZED = 0b0000_0010;
596        /// Token has been initialized: `C_InitToken` has been called (`CKF_TOKEN_INITIALIZED`).
597        const TOKEN_INITIALIZED   = 0b0000_0100;
598        /// Token is write-protected (`CKF_WRITE_PROTECTED`).
599        const WRITE_PROTECTED     = 0b0000_1000;
600    }
601}
602
603/// PKCS#11 token information.
604#[derive(Debug, Clone, Serialize, Deserialize)]
605pub struct Pkcs11TokenInfo {
606    /// Token label (padded to 32 bytes in PKCS#11, trimmed here).
607    pub label: String,
608    /// Manufacturer ID.
609    pub manufacturer: String,
610    /// Token model.
611    pub model: String,
612    /// Token serial number.
613    pub serial: String,
614    /// Firmware version string.
615    pub firmware_version: String,
616    /// Token capability flags.
617    pub flags: Pkcs11TokenFlags,
618}
619
620/// Opaque PKCS#11 mechanism identifier.
621///
622/// An enum would be incomplete (hundreds of `CKM_*` values, plus vendor extensions).
623/// A string like `"CKM_RSA_PKCS_KEY_PAIR_GEN"` is honest about what we expose.
624/// Used only for capability checking ("does this token support X?"), not dispatch.
625#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
626#[serde(transparent)]
627pub struct Pkcs11Mechanism(String);
628
629impl Pkcs11Mechanism {
630    /// Create a new `Pkcs11Mechanism` from a `CKM_*` name string.
631    pub fn new(name: impl Into<String>) -> Self {
632        Self(name.into())
633    }
634
635    /// Return the mechanism name as a string slice.
636    pub fn as_str(&self) -> &str {
637        &self.0
638    }
639}
640
641impl fmt::Display for Pkcs11Mechanism {
642    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
643        write!(f, "{}", self.0)
644    }
645}
646
647/// TPM (Trusted Platform Module) information.
648#[derive(Debug, Clone, Serialize, Deserialize)]
649pub struct TpmInfo {
650    /// TPM specification version (e.g., "2.0").
651    pub version: String,
652    /// TPM manufacturer identifier.
653    pub manufacturer: String,
654    /// Firmware version (if available).
655    pub firmware_version: Option<String>,
656}
657
658/// PCR (Platform Configuration Register) value.
659///
660/// The `value` field encodes both the algorithm and the hash in the format
661/// `"algorithm:hexhash"` (e.g., `"sha256:abc123..."`).
662#[derive(Debug, Clone, Serialize, Deserialize)]
663pub struct PcrValue {
664    /// PCR index (0-23 typically).
665    pub index: u8,
666    /// PCR value in the format `"algorithm:hexhash"` (e.g., `"sha256:abc123..."`).
667    pub value: String,
668}
669
670/// Backend configuration entry from a ceremony file.
671///
672/// The `provider` field identifies the backend by its self-declared name
673/// (e.g., `"software"`, `"yubikey"`). The remaining YAML keys are flattened
674/// into `extra` for backend-specific deserialization.
675///
676/// Each backend crate defines its own config struct and deserializes from
677/// `extra` at startup. This inverts the prior design where the model listed
678/// concrete backend variants; backends now declare themselves.
679#[derive(Debug, Clone, Serialize, Deserialize)]
680pub struct BackendConfig {
681    /// Backend provider identifier as a lowercase string (e.g., `"software"`).
682    pub provider: String,
683    /// Backend-specific configuration key/value pairs.
684    #[serde(flatten)]
685    pub extra: serde_json::Value,
686}
687
688#[cfg(test)]
689mod tests {
690    use super::*;
691
692    #[test]
693    fn key_algorithm_serde_roundtrip() {
694        // Serde uses Display strings via `serde(into/try_from)`. These are the canonical
695        // strings for ceremony YAML `algorithm:` fields and transcripts.
696        let cases: &[(KeyAlgorithm, &str)] = &[
697            (KeyAlgorithm::Rsa2048, "\"RSA-2048\""),
698            (KeyAlgorithm::Rsa4096, "\"RSA-4096\""),
699            (KeyAlgorithm::EcdsaP256, "\"ECDSA-P256\""),
700            (KeyAlgorithm::EcdsaP384, "\"ECDSA-P384\""),
701            (KeyAlgorithm::Ed25519, "\"Ed25519\""),
702            (KeyAlgorithm::Aes128, "\"AES-128\""),
703            (KeyAlgorithm::Aes256, "\"AES-256\""),
704        ];
705        for &(variant, expected) in cases {
706            let serialized = serde_json::to_string(&variant).unwrap();
707            assert_eq!(serialized, expected, "serialize {variant:?}");
708            let deserialized: KeyAlgorithm = serde_json::from_str(expected).unwrap();
709            assert_eq!(deserialized, variant, "deserialize {expected}");
710        }
711    }
712
713    #[test]
714    fn wrap_algorithm_serde_roundtrip() {
715        // Serde uses Display strings via `serde(into/try_from)`. These strings appear
716        // in ceremony YAML `algorithm:` fields and transcripts.
717        let cases: &[(WrapAlgorithm, &str)] = &[
718            (WrapAlgorithm::CmsRsaCbc, "\"CMS-RSA-CBC\""),
719            (WrapAlgorithm::CmsRsaGcm, "\"CMS-RSA-GCM\""),
720            (WrapAlgorithm::AesKeyWrap, "\"AES-KW\""),
721            (WrapAlgorithm::AesKeyWrapPad, "\"AES-KWP\""),
722            (WrapAlgorithm::RsaOaepSha256, "\"RSA-OAEP-SHA256\""),
723        ];
724        for &(variant, expected) in cases {
725            let serialized = serde_json::to_string(&variant).unwrap();
726            assert_eq!(serialized, expected, "serialize {variant:?}");
727            let deserialized: WrapAlgorithm = serde_json::from_str(expected).unwrap();
728            assert_eq!(deserialized, variant, "deserialize {expected}");
729        }
730    }
731
732    #[test]
733    fn wrap_algorithm_from_str_rejects_unknown() {
734        assert!("CMS-RSA-XTS".parse::<WrapAlgorithm>().is_err());
735        assert!("".parse::<WrapAlgorithm>().is_err());
736        assert!(
737            "cms-rsa-gcm".parse::<WrapAlgorithm>().is_err(),
738            "must be case-sensitive"
739        );
740    }
741
742    #[test]
743    fn piv_slot_retired_enforces_valid_range() {
744        // Indices 0–19 map to PIV key references 0x82–0x95.
745        // An out-of-range index would send an invalid reference to PIV hardware.
746        assert!(PivSlot::retired(0).is_some(), "index 0 must be valid");
747        assert!(
748            PivSlot::retired(19).is_some(),
749            "index 19 must be valid (last slot 0x95)"
750        );
751        assert!(
752            PivSlot::retired(20).is_none(),
753            "index 20 must be rejected (out of range)"
754        );
755        assert!(
756            PivSlot::retired(255).is_none(),
757            "index 255 must be rejected"
758        );
759    }
760
761    #[test]
762    fn key_policy_default_is_secure() {
763        // The default is documented as the most secure configuration for ceremony signing keys.
764        // A regression here (e.g., extractable=true) is a silent security downgrade.
765        let policy = KeyPolicy::default();
766        assert!(
767            policy.persistent,
768            "ceremony keys must persist across sessions"
769        );
770        assert!(policy.sensitive, "ceremony keys must be marked sensitive");
771        assert!(
772            !policy.extractable,
773            "ceremony keys must not be extractable by default"
774        );
775        assert!(policy.usages.contains(KeyUsages::SIGN));
776        assert!(policy.usages.contains(KeyUsages::VERIFY));
777        assert!(
778            !policy.usages.contains(KeyUsages::ENCRYPT),
779            "signing-only default must not permit encryption"
780        );
781        assert!(
782            !policy.usages.contains(KeyUsages::WRAP),
783            "signing-only default must not permit wrapping"
784        );
785    }
786}