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}