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