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}