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}