Skip to main content

rite_sdk/
types.rs

1//! Data types shared between the runtime and backend implementations.
2//!
3//! These types form the vocabulary of the backend interface: key specifications,
4//! algorithm identifiers, attestation evidence, and hardware-specific metadata.
5
6use serde::{Deserialize, Serialize};
7use std::fmt;
8
9use crate::backend::BackendError;
10use crate::key_material::PublicKeyDer;
11
12/// Opaque key identifier (backend-specific).
13///
14/// This is an opaque reference to a key managed by a backend. The internal
15/// format is backend-specific (e.g., `"slot_9c"` for `YubiKey` PIV, UUID for
16/// software backend).
17///
18/// Ceremony DSL never interprets the contents of a `KeyId`; it is purely a
19/// reference that gets passed back to the backend.
20#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
21#[serde(transparent)]
22pub struct KeyId(String);
23
24impl KeyId {
25    /// Create a new `KeyId` from a string.
26    pub fn new(s: impl Into<String>) -> Self {
27        Self(s.into())
28    }
29
30    /// Return the `KeyId` as a string slice.
31    pub fn as_str(&self) -> &str {
32        &self.0
33    }
34}
35
36impl AsRef<str> for KeyId {
37    fn as_ref(&self) -> &str {
38        &self.0
39    }
40}
41
42impl fmt::Display for KeyId {
43    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
44        write!(f, "{}", self.0)
45    }
46}
47
48impl From<String> for KeyId {
49    fn from(s: String) -> Self {
50        Self(s)
51    }
52}
53
54impl From<&str> for KeyId {
55    fn from(s: &str) -> Self {
56        Self(s.to_string())
57    }
58}
59
60/// Metadata returned after key generation or import.
61#[derive(Debug, Clone, Serialize, Deserialize)]
62pub struct KeyMetadata {
63    /// Opaque backend-specific key identifier.
64    pub key_id: KeyId,
65    /// Key algorithm.
66    pub algorithm: KeyAlgorithm,
67    /// Human-readable label.
68    pub label: String,
69    /// Public key, if the backend exports one.
70    pub public_key: Option<PublicKeyDer>,
71    /// Key check value, for a symmetric key.
72    ///
73    /// The counterpart of `public_key`: what the key answers to in the
74    /// transcript and in the room. `None` for an asymmetric key, which is
75    /// named by its public half.
76    pub check_value: Option<KeyCheckValue>,
77    /// Attestation evidence (if backend supports attestation).
78    pub attestation: Option<Attestation>,
79}
80
81/// What a symmetric key answers to, in place of a public key.
82///
83/// AES-CMAC over a 16-byte zero block under the key, leftmost three bytes, as
84/// specified for AES keys in ANSI X9.24-1 Annex A and used by TR-31. It is
85/// what an HSM displays and what custodians read back, so a transcript
86/// carrying it records the value the room checked rather than a parallel one
87/// only Rite can compute.
88///
89/// Three bytes, not a digest of the key. A full-length hash of a secret is
90/// what rite#126 removed from the transcript, and a reader cannot tell by
91/// looking whether such a value came from a 256-bit key or a 4-digit PIN. The
92/// truncation here is the part that makes it evident.
93///
94/// Renders as `cmac-aes:763cbc`. The method is in the string because the
95/// older convention of encrypting a zero block in ECB is still in use and
96/// gives a different answer for the same key.
97///
98/// ```
99/// use rite_sdk::KeyCheckValue;
100///
101/// // A backend computes the CMAC; this keeps the leftmost bytes of it.
102/// let cmac = [0x76, 0x3c, 0xbc, 0xde, 0x81, 0xdf, 0x91, 0x31];
103/// let kcv = KeyCheckValue::from_cmac(&cmac)?;
104///
105/// assert_eq!(kcv.to_string(), "cmac-aes:763cbc");
106/// assert_eq!(kcv.as_bytes(), &[0x76, 0x3c, 0xbc]);
107///
108/// // It round-trips through the string a transcript records.
109/// assert_eq!("cmac-aes:763cbc".parse::<KeyCheckValue>()?, kcv);
110/// // Uppercase hex is a different string for the same bytes, so it is not one.
111/// assert!("cmac-aes:763CBC".parse::<KeyCheckValue>().is_err());
112/// # Ok::<(), rite_sdk::ParseError>(())
113/// ```
114#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
115#[serde(into = "String", try_from = "String")]
116pub struct KeyCheckValue([u8; Self::LEN]);
117
118impl KeyCheckValue {
119    /// Bytes kept from the CMAC output.
120    pub const LEN: usize = 3;
121
122    /// The name this construction goes by, and the string's prefix.
123    pub const METHOD: &'static str = "cmac-aes";
124
125    /// Keep the leftmost bytes of a CMAC output.
126    ///
127    /// # Errors
128    ///
129    /// If `cmac` is shorter than [`Self::LEN`], which no AES-CMAC is.
130    pub fn from_cmac(cmac: &[u8]) -> Result<Self, ParseError> {
131        cmac.get(..Self::LEN)
132            .and_then(|head| head.try_into().ok())
133            .map(Self)
134            .ok_or_else(|| ParseError(format!("a CMAC of {} bytes", cmac.len())))
135    }
136
137    /// The kept bytes.
138    #[must_use]
139    pub fn as_bytes(&self) -> &[u8; Self::LEN] {
140        &self.0
141    }
142}
143
144impl fmt::Display for KeyCheckValue {
145    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
146        write!(f, "{}:", Self::METHOD)?;
147        for byte in self.0 {
148            write!(f, "{byte:02x}")?;
149        }
150        Ok(())
151    }
152}
153
154impl std::str::FromStr for KeyCheckValue {
155    type Err = ParseError;
156
157    fn from_str(s: &str) -> Result<Self, Self::Err> {
158        let hex = s
159            .strip_prefix(Self::METHOD)
160            .and_then(|rest| rest.strip_prefix(':'))
161            // Lowercase only, so the string a `KeyCheckValue` parses from is
162            // the string it renders back to and two records of one key
163            // compare as text.
164            .filter(|hex| {
165                hex.len() == 2 * Self::LEN
166                    && hex.bytes().all(|b| matches!(b, b'0'..=b'9' | b'a'..=b'f'))
167            })
168            .ok_or_else(|| ParseError(s.to_owned()))?;
169        let (pairs, _) = hex.as_bytes().as_chunks::<2>();
170        let mut bytes = [0u8; Self::LEN];
171        for (byte, pair) in bytes.iter_mut().zip(pairs) {
172            let pair = std::str::from_utf8(pair).map_err(|_| ParseError(s.to_owned()))?;
173            *byte = u8::from_str_radix(pair, 16).map_err(|_| ParseError(s.to_owned()))?;
174        }
175        Ok(Self(bytes))
176    }
177}
178
179impl From<KeyCheckValue> for String {
180    fn from(kcv: KeyCheckValue) -> String {
181        kcv.to_string()
182    }
183}
184
185impl TryFrom<String> for KeyCheckValue {
186    type Error = ParseError;
187
188    fn try_from(s: String) -> Result<Self, Self::Error> {
189        s.parse()
190    }
191}
192
193/// Key algorithm.
194#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
195#[serde(into = "String", try_from = "String")]
196#[non_exhaustive]
197pub enum KeyAlgorithm {
198    /// RSA 2048-bit key.
199    Rsa2048,
200    /// RSA 4096-bit key.
201    Rsa4096,
202    /// ECDSA with P-256 curve (secp256r1).
203    EcdsaP256,
204    /// ECDSA with P-384 curve (secp384r1).
205    EcdsaP384,
206    /// Ed25519 (`EdDSA`).
207    Ed25519,
208    /// ML-DSA-44, module-lattice signature at NIST security category 2 (FIPS 204).
209    MlDsa44,
210    /// ML-DSA-65, module-lattice signature at NIST security category 3 (FIPS 204).
211    MlDsa65,
212    /// ML-DSA-87, module-lattice signature at NIST security category 5 (FIPS 204).
213    MlDsa87,
214    /// ML-KEM-512, module-lattice KEM at NIST security category 1 (FIPS 203).
215    ///
216    /// A KEM encapsulates, it does not sign. Such a key can receive a wrap and
217    /// nothing else.
218    MlKem512,
219    /// ML-KEM-768, module-lattice KEM at NIST security category 3 (FIPS 203).
220    MlKem768,
221    /// ML-KEM-1024, module-lattice KEM at NIST security category 5 (FIPS 203).
222    MlKem1024,
223    /// AES 128-bit symmetric key.
224    Aes128,
225    /// AES 256-bit symmetric key.
226    Aes256,
227}
228
229impl KeyAlgorithm {
230    /// The signature algorithm to use with this key unless told otherwise.
231    ///
232    /// `None` for symmetric keys, which sign nothing.
233    ///
234    /// A key algorithm does not always determine a signature algorithm: RSA
235    /// keys work with both PKCS#1 v1.5 and PSS, and this picks v1.5 for
236    /// interoperability. Everywhere else the pairing is forced, either by the
237    /// curve's matching digest strength (RFC 5480) or by the scheme naming its
238    /// own digest (Ed25519, ML-DSA).
239    #[must_use]
240    pub fn default_sign_algorithm(self) -> Option<SignAlgorithm> {
241        match self {
242            KeyAlgorithm::Rsa2048 | KeyAlgorithm::Rsa4096 => Some(SignAlgorithm::RsaPkcs1Sha256),
243            KeyAlgorithm::EcdsaP256 => Some(SignAlgorithm::EcdsaSha256),
244            KeyAlgorithm::EcdsaP384 => Some(SignAlgorithm::EcdsaSha384),
245            KeyAlgorithm::Ed25519 => Some(SignAlgorithm::Ed25519),
246            KeyAlgorithm::MlDsa44 => Some(SignAlgorithm::MlDsa44),
247            KeyAlgorithm::MlDsa65 => Some(SignAlgorithm::MlDsa65),
248            KeyAlgorithm::MlDsa87 => Some(SignAlgorithm::MlDsa87),
249            KeyAlgorithm::MlKem512
250            | KeyAlgorithm::MlKem768
251            | KeyAlgorithm::MlKem1024
252            | KeyAlgorithm::Aes128
253            | KeyAlgorithm::Aes256 => None,
254        }
255    }
256
257    /// Whether a key of this algorithm can be the recipient of a wrap.
258    ///
259    /// A recipient has to be able to receive a content-encryption key, by key
260    /// transport, key agreement, or encapsulation. A signature algorithm can
261    /// do none of the three, however good a key it otherwise is, and this
262    /// answers the question before a ceremony reaches the wrap step.
263    #[must_use]
264    pub fn can_receive_wrap(self) -> bool {
265        match self {
266            KeyAlgorithm::Rsa2048
267            | KeyAlgorithm::Rsa4096
268            | KeyAlgorithm::EcdsaP256
269            | KeyAlgorithm::EcdsaP384
270            | KeyAlgorithm::MlKem512
271            | KeyAlgorithm::MlKem768
272            | KeyAlgorithm::MlKem1024 => true,
273
274            // Ed25519 and ML-DSA sign and nothing else. A symmetric key is a
275            // pre-shared KEK, which is a different recipient form than the
276            // ones Rite produces.
277            KeyAlgorithm::Ed25519
278            | KeyAlgorithm::MlDsa44
279            | KeyAlgorithm::MlDsa65
280            | KeyAlgorithm::MlDsa87
281            | KeyAlgorithm::Aes128
282            | KeyAlgorithm::Aes256 => false,
283        }
284    }
285
286    /// Whether a key of this algorithm is one secret with no public half.
287    ///
288    /// ```
289    /// use rite_sdk::KeyAlgorithm;
290    ///
291    /// assert!(KeyAlgorithm::Aes256.is_symmetric());
292    /// assert_eq!(KeyAlgorithm::Aes256.key_bytes(), Some(32));
293    ///
294    /// assert!(!KeyAlgorithm::EcdsaP256.is_symmetric());
295    /// assert_eq!(KeyAlgorithm::EcdsaP256.key_bytes(), None);
296    /// ```
297    ///
298    /// This is the discriminator for everything that assumes a keypair: there
299    /// is no public key to export, no SPKI to fingerprint, and no certificate
300    /// to issue. A backend answers for such a key with a
301    /// [`KeyCheckValue`](crate::KeyCheckValue) instead.
302    #[must_use]
303    pub fn is_symmetric(self) -> bool {
304        self.key_bytes().is_some()
305    }
306
307    /// The length of the secret, for an algorithm whose key is raw bytes.
308    ///
309    /// `None` for an asymmetric algorithm, whose key has no single length a
310    /// caller could ask for.
311    #[must_use]
312    pub fn key_bytes(self) -> Option<usize> {
313        match self {
314            KeyAlgorithm::Aes128 => Some(16),
315            KeyAlgorithm::Aes256 => Some(32),
316            KeyAlgorithm::Rsa2048
317            | KeyAlgorithm::Rsa4096
318            | KeyAlgorithm::EcdsaP256
319            | KeyAlgorithm::EcdsaP384
320            | KeyAlgorithm::Ed25519
321            | KeyAlgorithm::MlDsa44
322            | KeyAlgorithm::MlDsa65
323            | KeyAlgorithm::MlDsa87
324            | KeyAlgorithm::MlKem512
325            | KeyAlgorithm::MlKem768
326            | KeyAlgorithm::MlKem1024 => None,
327        }
328    }
329}
330
331impl fmt::Display for KeyAlgorithm {
332    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
333        match self {
334            KeyAlgorithm::Rsa2048 => write!(f, "RSA-2048"),
335            KeyAlgorithm::Rsa4096 => write!(f, "RSA-4096"),
336            KeyAlgorithm::EcdsaP256 => write!(f, "ECDSA-P256"),
337            KeyAlgorithm::EcdsaP384 => write!(f, "ECDSA-P384"),
338            KeyAlgorithm::Ed25519 => write!(f, "Ed25519"),
339            KeyAlgorithm::MlDsa44 => write!(f, "ML-DSA-44"),
340            KeyAlgorithm::MlDsa65 => write!(f, "ML-DSA-65"),
341            KeyAlgorithm::MlDsa87 => write!(f, "ML-DSA-87"),
342            KeyAlgorithm::MlKem512 => write!(f, "ML-KEM-512"),
343            KeyAlgorithm::MlKem768 => write!(f, "ML-KEM-768"),
344            KeyAlgorithm::MlKem1024 => write!(f, "ML-KEM-1024"),
345            KeyAlgorithm::Aes128 => write!(f, "AES-128"),
346            KeyAlgorithm::Aes256 => write!(f, "AES-256"),
347        }
348    }
349}
350
351/// Error returned when parsing an SDK algorithm identifier from a string fails.
352///
353/// Shared across all algorithm enums in this crate.
354#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
355#[error("unknown value: {0:?}")]
356pub struct ParseError(String);
357
358impl std::str::FromStr for KeyAlgorithm {
359    type Err = ParseError;
360
361    fn from_str(s: &str) -> Result<Self, Self::Err> {
362        match s {
363            "RSA-2048" => Ok(Self::Rsa2048),
364            "RSA-4096" => Ok(Self::Rsa4096),
365            "ECDSA-P256" => Ok(Self::EcdsaP256),
366            "ECDSA-P384" => Ok(Self::EcdsaP384),
367            "Ed25519" => Ok(Self::Ed25519),
368            "ML-DSA-44" => Ok(Self::MlDsa44),
369            "ML-DSA-65" => Ok(Self::MlDsa65),
370            "ML-DSA-87" => Ok(Self::MlDsa87),
371            "ML-KEM-512" => Ok(Self::MlKem512),
372            "ML-KEM-768" => Ok(Self::MlKem768),
373            "ML-KEM-1024" => Ok(Self::MlKem1024),
374            "AES-128" => Ok(Self::Aes128),
375            "AES-256" => Ok(Self::Aes256),
376            _ => Err(ParseError(s.to_owned())),
377        }
378    }
379}
380
381impl From<KeyAlgorithm> for String {
382    fn from(a: KeyAlgorithm) -> String {
383        a.to_string()
384    }
385}
386
387impl TryFrom<String> for KeyAlgorithm {
388    type Error = ParseError;
389
390    fn try_from(s: String) -> Result<Self, Self::Error> {
391        s.parse()
392    }
393}
394
395bitflags::bitflags! {
396    /// Key usage flags. Maps to PKCS#11 `CKA_SIGN`, `CKA_VERIFY`, etc.
397    ///
398    /// PIV ignores these (slot determines usage). PKCS#11 requires them at creation.
399    /// Using bitflags rather than 7 bools: more compact for serialization, makes
400    /// set operations natural, and avoids nonsensical bool combinations.
401    #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
402    pub struct KeyUsages: u8 {
403        /// Key may be used to sign data.
404        const SIGN    = 0b0000_0001;
405        /// Key may be used to verify signatures.
406        const VERIFY  = 0b0000_0010;
407        /// Key may be used to encrypt data.
408        const ENCRYPT = 0b0000_0100;
409        /// Key may be used to decrypt data.
410        const DECRYPT = 0b0000_1000;
411        /// Key may be used to wrap other keys.
412        const WRAP    = 0b0001_0000;
413        /// Key may be used to unwrap other keys.
414        const UNWRAP  = 0b0010_0000;
415        /// Key may be used to derive other keys.
416        const DERIVE  = 0b0100_0000;
417    }
418}
419
420impl KeyUsages {
421    /// Every usage, paired with the name a ceremony writes for it.
422    ///
423    /// The names are PKCS#11 vocabulary. They say what a key is permitted to do
424    /// at the token, which is a different question from the `KeyUsage`
425    /// extension a certificate carries.
426    pub const NAMED: [(&'static str, KeyUsages); 7] = [
427        ("sign", KeyUsages::SIGN),
428        ("verify", KeyUsages::VERIFY),
429        ("encrypt", KeyUsages::ENCRYPT),
430        ("decrypt", KeyUsages::DECRYPT),
431        ("wrap", KeyUsages::WRAP),
432        ("unwrap", KeyUsages::UNWRAP),
433        ("derive", KeyUsages::DERIVE),
434    ];
435
436    /// Look up a single usage by its ceremony name.
437    ///
438    /// Named to avoid colliding with the bitflags-generated `from_name`,
439    /// which matches on the `SCREAMING_CASE` constant names instead.
440    #[must_use]
441    pub fn usage_named(name: &str) -> Option<Self> {
442        Self::NAMED
443            .iter()
444            .find(|(candidate, _)| *candidate == name)
445            .map(|(_, usage)| *usage)
446    }
447
448    /// The names of the usages in this set, in declaration order.
449    #[must_use]
450    pub fn names(self) -> Vec<&'static str> {
451        Self::NAMED
452            .iter()
453            .filter(|(_, usage)| self.contains(*usage))
454            .map(|(name, _)| *name)
455            .collect()
456    }
457
458    /// Every usage name a ceremony may write, for a message listing what is
459    /// available.
460    #[must_use]
461    pub fn all_names() -> Vec<&'static str> {
462        Self::NAMED.iter().map(|(name, _)| *name).collect()
463    }
464
465    /// The usages in this set as one phrase, for an error message.
466    #[must_use]
467    pub fn describe(self) -> String {
468        let names = self.names();
469        if names.is_empty() {
470            "nothing".to_string()
471        } else {
472            names.join(", ")
473        }
474    }
475}
476
477/// Security and usage policy for a generated or imported key.
478///
479/// Backends that cannot honour a requested policy MUST return
480/// `BackendError::OperationNotPermitted` rather than silently ignoring it.
481/// For example, a PIV backend receiving `extractable: true` must reject it
482/// because PIV keys are always non-extractable by hardware design.
483// Each bool maps directly to a named PKCS#11 boolean attribute (CKA_TOKEN,
484// CKA_SENSITIVE, CKA_EXTRACTABLE, CKA_WRAP_WITH_TRUSTED). Converting these
485// to two-variant enums would add type noise with no semantic benefit.
486#[allow(clippy::struct_excessive_bools)]
487#[derive(Debug, Clone, Serialize, Deserialize)]
488pub struct KeyPolicy {
489    /// Key persists after session ends. Always `true` for PIV. `CKA_TOKEN` in PKCS#11.
490    pub persistent: bool,
491    /// Key material never revealed in plaintext. Always `true` for PIV. `CKA_SENSITIVE`.
492    pub sensitive: bool,
493    /// Key can be wrapped and exported. Always `false` for PIV. `CKA_EXTRACTABLE`.
494    pub extractable: bool,
495    /// Key can only be wrapped by a `CKA_TRUSTED` wrapping key. PKCS#11 `CKA_WRAP_WITH_TRUSTED`.
496    pub wrap_with_trusted_only: bool,
497    /// What operations this key is permitted to perform.
498    pub usages: KeyUsages,
499}
500
501impl KeyPolicy {
502    /// Refuse an operation this policy does not allow.
503    ///
504    /// The policy is what the ceremony asked for at generation. A token
505    /// enforces it itself; a software backend has no token, so it calls this.
506    ///
507    /// # Errors
508    ///
509    /// Returns [`BackendError::OperationNotPermitted`] when `usage` is absent
510    /// from the policy, naming what the key may do instead.
511    pub fn require(&self, usage: KeyUsages, operation: &str) -> Result<(), BackendError> {
512        if self.usages.contains(usage) {
513            return Ok(());
514        }
515        Err(BackendError::OperationNotPermitted(format!(
516            "this key may not {operation}: its policy allows {}. \
517             Declare the usage under `policy:` on the step that generates it.",
518            self.usages.describe()
519        )))
520    }
521}
522
523impl KeyPolicy {
524    /// What a key of this algorithm may do when the ceremony says nothing.
525    ///
526    /// A generation step should call this rather than [`Default`], which is
527    /// the signing default and gives a symmetric key two usages it can never
528    /// have. Rite holds a symmetric key as a key-encryption key and nothing
529    /// else, so wrap and unwrap is the whole of what one is for here.
530    ///
531    /// ```
532    /// use rite_sdk::{KeyAlgorithm, KeyPolicy, KeyUsages};
533    ///
534    /// let signing = KeyPolicy::default_for(KeyAlgorithm::EcdsaP256);
535    /// assert_eq!(signing.usages, KeyUsages::SIGN | KeyUsages::VERIFY);
536    ///
537    /// let kek = KeyPolicy::default_for(KeyAlgorithm::Aes256);
538    /// assert_eq!(kek.usages, KeyUsages::WRAP | KeyUsages::UNWRAP);
539    ///
540    /// // Everything else is the restrictive default either way.
541    /// assert!(!kek.extractable);
542    /// ```
543    #[must_use]
544    pub fn default_for(algorithm: KeyAlgorithm) -> Self {
545        if algorithm.is_symmetric() {
546            Self {
547                usages: KeyUsages::WRAP | KeyUsages::UNWRAP,
548                ..Self::default()
549            }
550        } else {
551            Self::default()
552        }
553    }
554}
555
556impl Default for KeyPolicy {
557    /// Most secure configuration for ceremony signing keys:
558    /// persistent=true, sensitive=true, extractable=false, sign+verify only.
559    fn default() -> Self {
560        Self {
561            persistent: true,
562            sensitive: true,
563            extractable: false,
564            wrap_with_trusted_only: false,
565            usages: KeyUsages::SIGN | KeyUsages::VERIFY,
566        }
567    }
568}
569
570/// Full specification for key generation or import.
571///
572/// Replaces the `(algorithm, label, slot_hint)` triplet with a structured type
573/// that can carry PKCS#11 policy attributes.
574#[derive(Debug, Clone, Serialize, Deserialize)]
575pub struct KeySpec {
576    /// Key algorithm.
577    pub algorithm: KeyAlgorithm,
578    /// Human-readable label.
579    pub label: String,
580    /// Security and usage policy.
581    pub policy: KeyPolicy,
582    /// Backend-specific location hint.
583    /// PIV: "9c" maps to Signature slot. PKCS#11: slot index or partition label.
584    /// Software: ignored.
585    pub location_hint: Option<String>,
586}
587
588/// PKCS#11 key security attributes read from an HSM after key generation.
589///
590/// Records HSM-managed attribute state as evidence of key provenance and
591/// policy compliance in the ceremony transcript.
592// Each bool maps directly to a named PKCS#11 boolean attribute (CKA_ALWAYS_SENSITIVE,
593// CKA_NEVER_EXTRACTABLE, CKA_SENSITIVE, CKA_EXTRACTABLE, CKA_WRAP_WITH_TRUSTED).
594// These are distinct concepts; collapsing them into enums would obscure the PKCS#11 semantics.
595#[allow(clippy::struct_excessive_bools)]
596#[derive(Debug, Clone, Serialize, Deserialize)]
597pub struct KeySecurityAttributes {
598    /// Key was ALWAYS sensitive: never had `CKA_SENSITIVE=false` since creation.
599    /// This is the primary provenance indicator for PKCS#11 keys.
600    pub always_sensitive: bool,
601    /// Key was NEVER extractable: never had `CKA_EXTRACTABLE=true` since creation.
602    pub never_extractable: bool,
603    /// Current value of `CKA_SENSITIVE`.
604    pub sensitive: bool,
605    /// Current value of `CKA_EXTRACTABLE`.
606    pub extractable: bool,
607    /// Key can only be wrapped by a key with `CKA_TRUSTED=true` (`CKA_WRAP_WITH_TRUSTED`).
608    pub wrap_with_trusted_only: bool,
609    /// What operations this key is permitted to perform (read from key object).
610    pub usages: KeyUsages,
611}
612
613/// Signature algorithm.
614#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
615#[serde(into = "String", try_from = "String")]
616#[non_exhaustive]
617pub enum SignAlgorithm {
618    /// RSASSA-PKCS1-v1_5 with SHA-256.
619    RsaPkcs1Sha256,
620    /// RSASSA-PSS with SHA-256.
621    RsaPssSha256,
622    /// ECDSA with SHA-256.
623    EcdsaSha256,
624    /// ECDSA with SHA-384.
625    EcdsaSha384,
626    /// Ed25519 (pure `EdDSA`, no hash function).
627    Ed25519,
628    /// ML-DSA-44 (pure, no pre-hash). FIPS 204.
629    MlDsa44,
630    /// ML-DSA-65 (pure, no pre-hash). FIPS 204.
631    MlDsa65,
632    /// ML-DSA-87 (pure, no pre-hash). FIPS 204.
633    MlDsa87,
634}
635
636impl SignAlgorithm {
637    /// A representative key algorithm for this signature algorithm.
638    ///
639    /// Used where a signature request must be turned into a concrete key
640    /// algorithm: selecting a card algorithm, or minting a stand-in key for a
641    /// rehearsal. Keeping that mapping here means those callers cannot drift
642    /// apart from each other.
643    ///
644    /// The mapping is deliberately lossy for RSA: both RSA schemes answer
645    /// `Rsa2048`, because a signature algorithm does not name a modulus size.
646    /// Use [`accepts_key`](Self::accepts_key) to test whether a key may be used
647    /// with this algorithm; the equality `alg.key_algorithm() == key` is not
648    /// that test and rejects RSA-4096 keys.
649    #[must_use]
650    pub fn key_algorithm(self) -> KeyAlgorithm {
651        match self {
652            SignAlgorithm::RsaPkcs1Sha256 | SignAlgorithm::RsaPssSha256 => KeyAlgorithm::Rsa2048,
653            SignAlgorithm::EcdsaSha256 => KeyAlgorithm::EcdsaP256,
654            SignAlgorithm::EcdsaSha384 => KeyAlgorithm::EcdsaP384,
655            SignAlgorithm::Ed25519 => KeyAlgorithm::Ed25519,
656            SignAlgorithm::MlDsa44 => KeyAlgorithm::MlDsa44,
657            SignAlgorithm::MlDsa65 => KeyAlgorithm::MlDsa65,
658            SignAlgorithm::MlDsa87 => KeyAlgorithm::MlDsa87,
659        }
660    }
661
662    /// Whether a key of `key_algorithm` may be used with this signature algorithm.
663    ///
664    /// The compatibility check every signing backend needs, kept in one place.
665    /// Curve and parameter set are pinned: ECDSA-SHA256 will not take a P-384
666    /// key. Only the RSA schemes span more than one key algorithm, since they
667    /// are defined for any modulus size.
668    #[must_use]
669    pub fn accepts_key(self, key_algorithm: KeyAlgorithm) -> bool {
670        match self {
671            SignAlgorithm::RsaPkcs1Sha256 | SignAlgorithm::RsaPssSha256 => {
672                matches!(key_algorithm, KeyAlgorithm::Rsa2048 | KeyAlgorithm::Rsa4096)
673            }
674            // Every other scheme pins exactly one key algorithm, already named
675            // by `key_algorithm`. Reuse it rather than repeating the pairing.
676            SignAlgorithm::EcdsaSha256
677            | SignAlgorithm::EcdsaSha384
678            | SignAlgorithm::Ed25519
679            | SignAlgorithm::MlDsa44
680            | SignAlgorithm::MlDsa65
681            | SignAlgorithm::MlDsa87 => self.key_algorithm() == key_algorithm,
682        }
683    }
684}
685
686impl fmt::Display for SignAlgorithm {
687    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
688        match self {
689            SignAlgorithm::RsaPkcs1Sha256 => write!(f, "RSA-PKCS1-SHA256"),
690            SignAlgorithm::RsaPssSha256 => write!(f, "RSA-PSS-SHA256"),
691            SignAlgorithm::EcdsaSha256 => write!(f, "ECDSA-SHA256"),
692            SignAlgorithm::EcdsaSha384 => write!(f, "ECDSA-SHA384"),
693            SignAlgorithm::Ed25519 => write!(f, "Ed25519"),
694            SignAlgorithm::MlDsa44 => write!(f, "ML-DSA-44"),
695            SignAlgorithm::MlDsa65 => write!(f, "ML-DSA-65"),
696            SignAlgorithm::MlDsa87 => write!(f, "ML-DSA-87"),
697        }
698    }
699}
700
701impl std::str::FromStr for SignAlgorithm {
702    type Err = ParseError;
703
704    fn from_str(s: &str) -> Result<Self, Self::Err> {
705        match s {
706            "RSA-PKCS1-SHA256" => Ok(Self::RsaPkcs1Sha256),
707            "RSA-PSS-SHA256" => Ok(Self::RsaPssSha256),
708            "ECDSA-SHA256" => Ok(Self::EcdsaSha256),
709            "ECDSA-SHA384" => Ok(Self::EcdsaSha384),
710            "Ed25519" => Ok(Self::Ed25519),
711            "ML-DSA-44" => Ok(Self::MlDsa44),
712            "ML-DSA-65" => Ok(Self::MlDsa65),
713            "ML-DSA-87" => Ok(Self::MlDsa87),
714            _ => Err(ParseError(s.to_owned())),
715        }
716    }
717}
718
719impl From<SignAlgorithm> for String {
720    fn from(a: SignAlgorithm) -> String {
721        a.to_string()
722    }
723}
724
725impl TryFrom<String> for SignAlgorithm {
726    type Error = ParseError;
727
728    fn try_from(s: String) -> Result<Self, Self::Error> {
729        s.parse()
730    }
731}
732
733/// An ASN.1 object identifier in dotted-decimal form.
734///
735/// Wrapping evidence is a set of algorithm identifiers read back out of the
736/// produced artifact, so the OID is the value that gets recorded. Held as text
737/// because that is what a transcript reader compares against a registry.
738#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
739#[serde(into = "String", try_from = "String")]
740pub struct Oid(String);
741
742impl Oid {
743    /// Parse a dotted-decimal OID.
744    ///
745    /// # Errors
746    ///
747    /// Returns [`ParseError`] unless the value is two or more arcs of decimal
748    /// digits separated by dots.
749    pub fn new(value: &str) -> Result<Self, ParseError> {
750        let arcs: Vec<&str> = value.split('.').collect();
751        let well_formed = arcs.len() >= 2
752            && arcs
753                .iter()
754                .all(|arc| !arc.is_empty() && arc.bytes().all(|b| b.is_ascii_digit()));
755        if well_formed {
756            Ok(Self(value.to_owned()))
757        } else {
758            Err(ParseError(value.to_owned()))
759        }
760    }
761
762    /// The dotted-decimal form.
763    #[must_use]
764    pub fn as_str(&self) -> &str {
765        &self.0
766    }
767}
768
769impl fmt::Display for Oid {
770    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
771        f.write_str(&self.0)
772    }
773}
774
775impl From<Oid> for String {
776    fn from(oid: Oid) -> String {
777        oid.0
778    }
779}
780
781impl TryFrom<String> for Oid {
782    type Error = ParseError;
783
784    fn try_from(s: String) -> Result<Self, Self::Error> {
785        Oid::new(&s)
786    }
787}
788
789/// Well-known OIDs the wrapping paths produce or check.
790pub mod oid {
791    /// `rsaEncryption`, RSAES-PKCS1-v1.5 key transport (RFC 8017).
792    pub const RSA_ENCRYPTION: &str = "1.2.840.113549.1.1.1";
793    /// `id-RSAES-OAEP` (RFC 8017).
794    pub const RSAES_OAEP: &str = "1.2.840.113549.1.1.7";
795    /// `dhSinglePass-stdDH-sha1kdf-scheme`, one-pass ECDH with the X9.63 KDF
796    /// over SHA-1 (RFC 5753).
797    pub const DH_SINGLE_PASS_STDDH_SHA1KDF: &str = "1.3.133.16.840.63.0.2";
798    /// `id-aes256-GCM` (RFC 5084).
799    pub const AES_256_GCM: &str = "2.16.840.1.101.3.4.1.46";
800    /// `id-aes256-CBC` (RFC 3565).
801    pub const AES_256_CBC: &str = "2.16.840.1.101.3.4.1.42";
802    /// `id-aes256-wrap`, AES Key Wrap with a 256-bit KEK (RFC 3394).
803    pub const AES_256_WRAP: &str = "2.16.840.1.101.3.4.1.45";
804    /// `id-aes128-wrap`, AES Key Wrap with a 128-bit KEK (RFC 3394).
805    pub const AES_128_WRAP: &str = "2.16.840.1.101.3.4.1.5";
806    /// `id-aes256-wrap-pad`, AES Key Wrap with Padding (RFC 5649).
807    pub const AES_256_WRAP_PAD: &str = "2.16.840.1.101.3.4.1.48";
808    /// `id-aes128-wrap-pad`, AES Key Wrap with Padding (RFC 5649).
809    pub const AES_128_WRAP_PAD: &str = "2.16.840.1.101.3.4.1.8";
810}
811
812/// How a CMS structure conveys the content-encryption key to its recipient.
813///
814/// The variants are the `RecipientInfo` CHOICE of RFC 5652 §6.2, plus the
815/// KEM alternative RFC 9629 carries inside `OtherRecipientInfo`.
816#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
817#[serde(rename_all = "snake_case")]
818#[non_exhaustive]
819pub enum RecipientInfoKind {
820    /// `KeyTransRecipientInfo`: the CEK is encrypted under the recipient's
821    /// public key.
822    Ktri,
823    /// `KeyAgreeRecipientInfo`: a shared secret is agreed with the recipient's
824    /// key, run through a KDF, and used to wrap the CEK.
825    Kari,
826    /// `KEKRecipientInfo`: the CEK is wrapped under a previously shared
827    /// symmetric key.
828    Kekri,
829    /// `KEMRecipientInfo` (RFC 9629), carried as `OtherRecipientInfo`.
830    Kemri,
831    /// `PasswordRecipientInfo`.
832    Pwri,
833    /// An `OtherRecipientInfo` this build does not recognise.
834    Ori,
835}
836
837impl fmt::Display for RecipientInfoKind {
838    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
839        match self {
840            RecipientInfoKind::Ktri => write!(f, "ktri"),
841            RecipientInfoKind::Kari => write!(f, "kari"),
842            RecipientInfoKind::Kekri => write!(f, "kekri"),
843            RecipientInfoKind::Kemri => write!(f, "kemri"),
844            RecipientInfoKind::Pwri => write!(f, "pwri"),
845            RecipientInfoKind::Ori => write!(f, "ori"),
846        }
847    }
848}
849
850/// What a wrap actually did.
851///
852/// The scheme a step requests names a family. The algorithms inside it move
853/// independently: the recipient's key type selects the encapsulation, the
854/// content cipher selects the KEK size, and the KDF digest is a library
855/// default rather than a property of any key.
856///
857/// Where the scheme is self-describing this is an observation, re-derivable
858/// from the blob alone, which is what makes it evidence. Where it is not, it
859/// is what the backend invoked. [`WrapScheme::is_self_describing`] is the
860/// difference, and `rite verify` reports the two differently.
861#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
862#[non_exhaustive]
863pub struct WrapDescription {
864    /// How the CEK reaches the recipient, for a scheme built on CMS.
865    ///
866    /// Absent for a raw mechanism, whose output carries no `RecipientInfo`
867    /// and no other ASN.1 structure.
868    #[serde(default, skip_serializing_if = "Option::is_none")]
869    pub recipient_info: Option<RecipientInfoKind>,
870    /// The key-encryption algorithm: key transport for `ktri`, the
871    /// key-agreement scheme for `kari`.
872    pub key_encryption_oid: Oid,
873    /// The KDF, where the encapsulation derives a KEK.
874    #[serde(default, skip_serializing_if = "Option::is_none")]
875    pub kdf_oid: Option<Oid>,
876    /// The algorithm wrapping the CEK under the derived KEK.
877    #[serde(default, skip_serializing_if = "Option::is_none")]
878    pub kek_wrap_oid: Option<Oid>,
879    /// The content cipher, for schemes that encrypt the key as CMS content.
880    #[serde(default, skip_serializing_if = "Option::is_none")]
881    pub content_encryption_oid: Option<Oid>,
882}
883
884impl WrapDescription {
885    /// Describe a CMS wrap by its recipient info and key-encryption algorithm.
886    #[must_use]
887    pub fn new(recipient_info: RecipientInfoKind, key_encryption_oid: Oid) -> Self {
888        Self {
889            recipient_info: Some(recipient_info),
890            key_encryption_oid,
891            kdf_oid: None,
892            kek_wrap_oid: None,
893            content_encryption_oid: None,
894        }
895    }
896
897    /// Describe a raw mechanism, which has no CMS structure around it.
898    #[must_use]
899    pub fn raw(key_encryption_oid: Oid) -> Self {
900        Self {
901            recipient_info: None,
902            key_encryption_oid,
903            kdf_oid: None,
904            kek_wrap_oid: None,
905            content_encryption_oid: None,
906        }
907    }
908
909    /// Record the KDF the encapsulation ran.
910    #[must_use]
911    pub fn with_kdf(mut self, oid: Oid) -> Self {
912        self.kdf_oid = Some(oid);
913        self
914    }
915
916    /// Record the algorithm that wrapped the CEK under the derived KEK.
917    #[must_use]
918    pub fn with_kek_wrap(mut self, oid: Oid) -> Self {
919        self.kek_wrap_oid = Some(oid);
920        self
921    }
922
923    /// Record the content cipher.
924    #[must_use]
925    pub fn with_content_encryption(mut self, oid: Oid) -> Self {
926        self.content_encryption_oid = Some(oid);
927        self
928    }
929
930    /// Whether the CMS content was encrypted under an authenticated cipher.
931    ///
932    /// True only for AES-256-GCM, the one authenticated cipher Rite writes, so
933    /// a foreign blob under another AEAD reads as unauthenticated here. It asks
934    /// about a CMS content cipher and nothing else, so it is `false` for every
935    /// raw mechanism, including ones that authenticate by other means.
936    #[must_use]
937    pub fn content_is_authenticated(&self) -> bool {
938        self.content_encryption_oid
939            .as_ref()
940            .is_some_and(|oid| oid.as_str() == oid::AES_256_GCM)
941    }
942}
943
944/// The wrapping scheme a step requests, or a backend reports.
945///
946/// A scheme names a container and the parts of it Rite fixes. What varies
947/// with the recipient is recorded in [`WrapDescription`] instead.
948#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
949#[serde(into = "String", try_from = "String")]
950#[non_exhaustive]
951pub enum WrapScheme {
952    /// CMS `AuthEnvelopedData` (RFC 5083) with AES-256-GCM content
953    /// encryption. Output: CMS `ContentInfo` DER.
954    ///
955    /// The encapsulation follows the recipient key: RSA takes key transport,
956    /// EC takes the RFC 5753 key-agreement path, and a KEM recipient takes the
957    /// RFC 9629 key-encapsulation path.
958    ///
959    /// `CMS-RSA-CBC`, the `EnvelopedData` variant with AES-256-CBC, was
960    /// removed. It carried no integrity protection, so unwrapping imported
961    /// whatever decrypted; PCI PIN v3.1 requirement 18-3 bans unauthenticated
962    /// key encryption. Restoring it would take an authenticated construction
963    /// around it, which is what the deployments that do use CBC (X9 TR-34,
964    /// AD CS key archival) wrap it in.
965    CmsAes256Gcm,
966    /// RSAES-OAEP with SHA-256, as raw ciphertext (RFC 8017).
967    ///
968    /// Output is exactly the modulus size and describes nothing about itself.
969    /// The payload ceiling is `k - 2*hLen - 2`: 190 bytes under RSA-2048, 446
970    /// under RSA-4096, which fits a symmetric key or an EC private key and
971    /// never fits an RSA one. [`Self::RsaAesKeyWrapSha256`] exists for that.
972    RsaOaepSha256,
973    /// RSA-AES key wrap with SHA-256 (PKCS#11 `CKM_RSA_AES_KEY_WRAP`).
974    ///
975    /// An ephemeral AES key under RSAES-OAEP, concatenated with the payload
976    /// under AES-KWP (RFC 5649). The OAEP part comes first and is exactly the
977    /// modulus size, which is how a recipient splits the two.
978    ///
979    /// This is the shape cloud KMS import expects, and it carries no size
980    /// ceiling. The digest is in the name because it is not recoverable from
981    /// the bytes and recipients differ: AWS names both `RSA_AES_KEY_WRAP_SHA_256`
982    /// and `RSA_AES_KEY_WRAP_SHA_1`, and Azure BYOK specifies SHA-1.
983    RsaAesKeyWrapSha256,
984    /// AES Key Wrap under a 128-bit KEK (RFC 3394, PKCS#11
985    /// `CKM_AES_KEY_WRAP`).
986    ///
987    /// The payload must be a multiple of 8 bytes and at least 16, which a
988    /// PKCS#8 private key is not, so this carries another symmetric key and
989    /// little else. [`Self::Aes128Kwp`] has no such rule.
990    Aes128Kw,
991    /// AES Key Wrap under a 256-bit KEK (RFC 3394).
992    Aes256Kw,
993    /// AES Key Wrap with Padding under a 128-bit KEK (RFC 5649, PKCS#11
994    /// `CKM_AES_KEY_WRAP_KWP`).
995    ///
996    /// Carries a payload of any length. This is what Thales recommends over
997    /// CBC for wrapping, and what `OpenStack` Barbican moved to in 2024 after
998    /// its hardcoded `CKM_AES_CBC_PAD` broke against Luna firmware in FIPS
999    /// mode.
1000    Aes128Kwp,
1001    /// AES Key Wrap with Padding under a 256-bit KEK (RFC 5649).
1002    Aes256Kwp,
1003}
1004
1005impl WrapScheme {
1006    /// The description this scheme fixes, where it fixes one.
1007    ///
1008    /// `None` for a self-describing scheme: what a CMS wrap did follows the
1009    /// recipient key, so it is read out of the artifact rather than known in
1010    /// advance. A raw mechanism has no such freedom, which is the same fact
1011    /// that makes its description an assertion.
1012    ///
1013    /// This is the one place that says what a raw scheme produces. A backend
1014    /// builds its description from here rather than restating the OIDs, and
1015    /// [`permits`](Self::permits) compares against it, so the two cannot
1016    /// disagree.
1017    #[must_use]
1018    pub fn fixed_description(self) -> Option<WrapDescription> {
1019        // Constructed directly rather than through the validating `Oid::new`:
1020        // these are this module's own constants, so there is no input to
1021        // reject and no error a caller could act on.
1022        let known = |value: &str| Oid(value.to_owned());
1023        match self {
1024            WrapScheme::CmsAes256Gcm => None,
1025            WrapScheme::RsaOaepSha256 => Some(WrapDescription::raw(known(oid::RSAES_OAEP))),
1026            WrapScheme::Aes128Kw => Some(WrapDescription::raw(known(oid::AES_128_WRAP))),
1027            WrapScheme::Aes256Kw => Some(WrapDescription::raw(known(oid::AES_256_WRAP))),
1028            WrapScheme::Aes128Kwp => Some(WrapDescription::raw(known(oid::AES_128_WRAP_PAD))),
1029            WrapScheme::Aes256Kwp => Some(WrapDescription::raw(known(oid::AES_256_WRAP_PAD))),
1030            WrapScheme::RsaAesKeyWrapSha256 => Some(
1031                WrapDescription::raw(known(oid::RSAES_OAEP))
1032                    .with_kek_wrap(known(oid::AES_256_WRAP_PAD)),
1033            ),
1034        }
1035    }
1036
1037    /// The one key algorithm this scheme wraps under, where it names one.
1038    ///
1039    /// ```
1040    /// use rite_sdk::{KeyAlgorithm, WrapScheme};
1041    ///
1042    /// assert_eq!(WrapScheme::Aes256Kwp.required_kek(), Some(KeyAlgorithm::Aes256));
1043    /// // CMS admits a family, so it names none.
1044    /// assert_eq!(WrapScheme::CmsAes256Gcm.required_kek(), None);
1045    /// ```
1046    ///
1047    /// `None` where the scheme admits a family rather than a single algorithm.
1048    /// The AES schemes name a KEK size, because the RFC 3394 and RFC 5649
1049    /// object identifiers are per-size and [`fixed_description`](Self::fixed_description)
1050    /// has only the scheme to go on. Naming it is also what makes a ceremony
1051    /// that expects a 256-bit KEK refuse a 128-bit one instead of wrapping
1052    /// under it in silence.
1053    #[must_use]
1054    pub fn required_kek(self) -> Option<KeyAlgorithm> {
1055        match self {
1056            WrapScheme::CmsAes256Gcm
1057            | WrapScheme::RsaOaepSha256
1058            | WrapScheme::RsaAesKeyWrapSha256 => None,
1059            WrapScheme::Aes128Kw | WrapScheme::Aes128Kwp => Some(KeyAlgorithm::Aes128),
1060            WrapScheme::Aes256Kw | WrapScheme::Aes256Kwp => Some(KeyAlgorithm::Aes256),
1061        }
1062    }
1063
1064    /// Whether a key of this algorithm can receive a wrap under this scheme.
1065    ///
1066    /// Narrower than [`KeyAlgorithm::can_receive_wrap`], which answers for CMS,
1067    /// where the recipient key selects the encapsulation. The RSA mechanisms
1068    /// accept nothing but RSA, and the AES mechanisms nothing but the KEK size
1069    /// in their name.
1070    ///
1071    /// A symmetric key passes here and still cannot arrive as `recipient:`,
1072    /// since that path takes a public key and a symmetric key has none. An
1073    /// AES scheme is reachable only under `wrapping_key:`.
1074    #[must_use]
1075    pub fn accepts_recipient(self, algorithm: KeyAlgorithm) -> bool {
1076        match self {
1077            WrapScheme::CmsAes256Gcm => algorithm.can_receive_wrap(),
1078            WrapScheme::RsaOaepSha256 | WrapScheme::RsaAesKeyWrapSha256 => match algorithm {
1079                KeyAlgorithm::Rsa2048 | KeyAlgorithm::Rsa4096 => true,
1080                KeyAlgorithm::EcdsaP256
1081                | KeyAlgorithm::EcdsaP384
1082                | KeyAlgorithm::Ed25519
1083                | KeyAlgorithm::MlDsa44
1084                | KeyAlgorithm::MlDsa65
1085                | KeyAlgorithm::MlDsa87
1086                | KeyAlgorithm::MlKem512
1087                | KeyAlgorithm::MlKem768
1088                | KeyAlgorithm::MlKem1024
1089                | KeyAlgorithm::Aes128
1090                | KeyAlgorithm::Aes256 => false,
1091            },
1092            WrapScheme::Aes128Kw
1093            | WrapScheme::Aes256Kw
1094            | WrapScheme::Aes128Kwp
1095            | WrapScheme::Aes256Kwp => self.required_kek() == Some(algorithm),
1096        }
1097    }
1098
1099    /// Whether `description` is one this scheme admits.
1100    ///
1101    /// The check is what keeps a recorded scheme from contradicting the
1102    /// artifact it labels. For a self-describing scheme it is a structural
1103    /// test against what was read out of the bytes; for a raw mechanism the
1104    /// description is fixed, so equality is the whole test.
1105    #[must_use]
1106    pub fn permits(self, description: &WrapDescription) -> bool {
1107        if let Some(fixed) = self.fixed_description() {
1108            return *description == fixed;
1109        }
1110        // `kekri` joins the three public-key encapsulations: the recipient is
1111        // a key both sides hold rather than one the wrap reaches by public
1112        // key, and the container and its guarantees are the same either way.
1113        let encapsulation_fits = matches!(
1114            description.recipient_info,
1115            Some(
1116                RecipientInfoKind::Ktri
1117                    | RecipientInfoKind::Kari
1118                    | RecipientInfoKind::Kemri
1119                    | RecipientInfoKind::Kekri
1120            )
1121        );
1122        description.content_is_authenticated() && encapsulation_fits
1123    }
1124
1125    /// Whether a verifier can re-derive the wrap's algorithms from the bytes.
1126    ///
1127    /// This is the line between evidence and assertion. A CMS artifact carries
1128    /// its own algorithm identifiers, so `rite verify` reads them back and
1129    /// compares them against the transcript; raw mechanism output carries
1130    /// nothing, so the recorded algorithms are what the backend reports having
1131    /// invoked and no offline check can confirm them.
1132    ///
1133    /// Matched exhaustively on purpose: a scheme added without answering this
1134    /// would otherwise default into being treated as evidence.
1135    #[must_use]
1136    pub fn is_self_describing(self) -> bool {
1137        match self {
1138            WrapScheme::CmsAes256Gcm => true,
1139            WrapScheme::RsaOaepSha256
1140            | WrapScheme::RsaAesKeyWrapSha256
1141            | WrapScheme::Aes128Kw
1142            | WrapScheme::Aes256Kw
1143            | WrapScheme::Aes128Kwp
1144            | WrapScheme::Aes256Kwp => false,
1145        }
1146    }
1147
1148    /// Whether the scheme carries content of any length, as opposed to a key.
1149    ///
1150    /// A container encrypts the content under its own key and addresses only
1151    /// that key to the recipient, so what goes in is arbitrary. A raw mechanism
1152    /// encrypts the payload directly under the recipient's key, which bounds it
1153    /// to roughly a key's size.
1154    ///
1155    /// A separate question from [`is_self_describing`](Self::is_self_describing),
1156    /// which draws the same line today. That one asks whether a verifier can
1157    /// re-derive the algorithms, and this one asks what may be put in. Keeping
1158    /// them apart is what lets them diverge honestly when a second container
1159    /// lands.
1160    ///
1161    /// Matched exhaustively on purpose: a scheme added without answering this
1162    /// would otherwise default into accepting a file.
1163    #[must_use]
1164    pub fn carries_content(self) -> bool {
1165        match self {
1166            WrapScheme::CmsAes256Gcm => true,
1167            WrapScheme::RsaOaepSha256
1168            | WrapScheme::RsaAesKeyWrapSha256
1169            | WrapScheme::Aes128Kw
1170            | WrapScheme::Aes256Kw
1171            | WrapScheme::Aes128Kwp
1172            | WrapScheme::Aes256Kwp => false,
1173        }
1174    }
1175}
1176
1177impl fmt::Display for WrapScheme {
1178    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1179        match self {
1180            WrapScheme::CmsAes256Gcm => write!(f, "CMS-AES-256-GCM"),
1181            WrapScheme::RsaOaepSha256 => write!(f, "RSA-OAEP-SHA256"),
1182            WrapScheme::RsaAesKeyWrapSha256 => write!(f, "RSA-AES-KEY-WRAP-SHA256"),
1183            WrapScheme::Aes128Kw => write!(f, "AES-128-KW"),
1184            WrapScheme::Aes256Kw => write!(f, "AES-256-KW"),
1185            WrapScheme::Aes128Kwp => write!(f, "AES-128-KWP"),
1186            WrapScheme::Aes256Kwp => write!(f, "AES-256-KWP"),
1187        }
1188    }
1189}
1190
1191impl std::str::FromStr for WrapScheme {
1192    type Err = ParseError;
1193
1194    fn from_str(s: &str) -> Result<Self, Self::Err> {
1195        match s {
1196            "CMS-AES-256-GCM" => Ok(Self::CmsAes256Gcm),
1197            "RSA-OAEP-SHA256" => Ok(Self::RsaOaepSha256),
1198            "RSA-AES-KEY-WRAP-SHA256" => Ok(Self::RsaAesKeyWrapSha256),
1199            "AES-128-KW" => Ok(Self::Aes128Kw),
1200            "AES-256-KW" => Ok(Self::Aes256Kw),
1201            "AES-128-KWP" => Ok(Self::Aes128Kwp),
1202            "AES-256-KWP" => Ok(Self::Aes256Kwp),
1203            _ => Err(ParseError(s.to_owned())),
1204        }
1205    }
1206}
1207
1208impl From<WrapScheme> for String {
1209    fn from(a: WrapScheme) -> String {
1210        a.to_string()
1211    }
1212}
1213
1214impl TryFrom<String> for WrapScheme {
1215    type Error = ParseError;
1216
1217    fn try_from(s: String) -> Result<Self, Self::Error> {
1218        s.parse()
1219    }
1220}
1221
1222/// Bytes in a container, the scheme that produced them, and what that
1223/// operation actually did.
1224///
1225/// The three are constructed together and read together: a scheme that does not
1226/// admit the description would label the bytes with something the bytes
1227/// contradict.
1228///
1229/// Private, because a container on its own says nothing about what is inside
1230/// it. [`WrappedKey`] and [`EncryptedData`] are the two public forms, and the
1231/// difference between them is the claim their step made, not the bytes.
1232#[derive(Debug, Clone, Serialize, Deserialize)]
1233#[serde(try_from = "ContainerRepr")]
1234struct Container {
1235    scheme: WrapScheme,
1236    description: WrapDescription,
1237    data: Vec<u8>,
1238}
1239
1240/// Deserialization shape for [`Container`], re-checked on the way in.
1241#[derive(Deserialize)]
1242struct ContainerRepr {
1243    scheme: WrapScheme,
1244    description: WrapDescription,
1245    data: Vec<u8>,
1246}
1247
1248impl TryFrom<ContainerRepr> for Container {
1249    type Error = IncoherentContainer;
1250
1251    fn try_from(repr: ContainerRepr) -> Result<Self, Self::Error> {
1252        Container::new(repr.scheme, repr.description, repr.data)
1253    }
1254}
1255
1256impl Container {
1257    fn new(
1258        scheme: WrapScheme,
1259        description: WrapDescription,
1260        data: Vec<u8>,
1261    ) -> Result<Self, IncoherentContainer> {
1262        if scheme.permits(&description) {
1263            Ok(Self {
1264                scheme,
1265                description,
1266                data,
1267            })
1268        } else {
1269            Err(IncoherentContainer {
1270                scheme,
1271                description,
1272            })
1273        }
1274    }
1275}
1276
1277/// A container was labelled with a scheme its own description contradicts.
1278#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
1279#[error("scheme {scheme} does not admit this container: {description:?}")]
1280pub struct IncoherentContainer {
1281    /// The scheme the bytes were labelled with.
1282    pub scheme: WrapScheme,
1283    /// What the artifact says was done.
1284    pub description: WrapDescription,
1285}
1286
1287/// Give a container newtype the three accessors and the checking constructor.
1288///
1289/// Both forms answer the same three questions about their bytes. What they do
1290/// not share is which steps accept them, which is the whole reason there are
1291/// two types rather than one.
1292macro_rules! container_newtype {
1293    ($name:ident, $what:literal) => {
1294        impl $name {
1295            #[doc = concat!("Pair ", $what, " with the scheme and the description of what was done.")]
1296            ///
1297            /// # Errors
1298            ///
1299            /// Returns [`IncoherentContainer`] if the scheme does not admit the
1300            /// description.
1301            pub fn new(
1302                scheme: WrapScheme,
1303                description: WrapDescription,
1304                data: Vec<u8>,
1305            ) -> Result<Self, IncoherentContainer> {
1306                Container::new(scheme, description, data).map(Self)
1307            }
1308
1309            #[doc = concat!("The scheme ", $what, " went into.")]
1310            #[must_use]
1311            pub fn scheme(&self) -> WrapScheme {
1312                self.0.scheme
1313            }
1314
1315            /// What the operation did, read back from the artifact.
1316            #[must_use]
1317            pub fn description(&self) -> &WrapDescription {
1318                &self.0.description
1319            }
1320
1321            /// The container's bytes.
1322            #[must_use]
1323            pub fn data(&self) -> &[u8] {
1324                &self.0.data
1325            }
1326        }
1327    };
1328}
1329
1330/// A key wrapped for transport.
1331///
1332/// Carries a custody claim: this key left a backend under protection. That is
1333/// what separates it from [`EncryptedData`], which holds the same shape of
1334/// bytes and claims nothing about custody.
1335#[derive(Debug, Clone, Serialize, Deserialize)]
1336#[serde(transparent)]
1337pub struct WrappedKey(Container);
1338
1339container_newtype!(WrappedKey, "a wrapped key");
1340
1341/// Encrypted content.
1342///
1343/// A distinct type from [`WrappedKey`] deliberately. The bytes of a container
1344/// are the same whether the content was a key or a document; what differs is
1345/// the claim the ceremony made about it, so a document that reached the
1346/// artifact store cannot be handed to a step that installs keys.
1347#[derive(Debug, Clone, Serialize, Deserialize)]
1348#[serde(transparent)]
1349pub struct EncryptedData(Container);
1350
1351container_newtype!(EncryptedData, "encrypted content");
1352
1353/// A content-encryption key in the two forms envelope encryption needs.
1354///
1355/// The plaintext is for the caller to encrypt with and drop. The wrapped copy
1356/// is what the container carries, and only the key-encryption key that produced
1357/// it opens that again.
1358///
1359/// This is what every key-protection device offers instead of a cipher: AWS KMS
1360/// calls it `GenerateDataKey`, PKCS#11 reaches it with `C_GenerateKey` followed
1361/// by `C_WrapKey`. The content never crosses the boundary, so a device that
1362/// protects a key it will not export does not also have to be fast enough to
1363/// encrypt a file.
1364pub struct DataKey {
1365    plaintext: zeroize::Zeroizing<Vec<u8>>,
1366    wrapped: Vec<u8>,
1367    protection: KeyProtection,
1368}
1369
1370/// How a data key was protected, in terms a container can record.
1371///
1372/// A container names the key-encryption algorithm in its own structure, so the
1373/// caller has to know which one the backend used. Asking the backend is the only
1374/// way: the protected bytes say nothing about themselves, and what a device does
1375/// to protect a key is a property of that device rather than of the request.
1376///
1377/// Every key-protection API in the field takes this as a parameter on the way
1378/// back: Azure Key Vault's `unwrapKey` requires `alg`, PKCS#11's `C_UnwrapKey`
1379/// requires a mechanism, and AWS KMS `Decrypt` requires `EncryptionAlgorithm`
1380/// for an asymmetric key. A pair of methods without it only works where one
1381/// implementation is both ends.
1382#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1383#[serde(rename_all = "snake_case")]
1384#[non_exhaustive]
1385pub enum KeyProtection {
1386    /// RFC 3394 AES Key Wrap under a symmetric KEK.
1387    ///
1388    /// What a CMS `KEKRecipientInfo` names as `id-aes128-wrap` or
1389    /// `id-aes256-wrap`, PKCS#11 as `CKM_AES_KEY_WRAP`, and Azure as `A256KW`.
1390    AesKeyWrap,
1391    /// RSAES-OAEP with SHA-256, under a public key.
1392    ///
1393    /// Azure `RSA-OAEP-256`, PKCS#11 `CKM_RSA_PKCS_OAEP`, AWS
1394    /// `RSAES_OAEP_SHA_256`.
1395    RsaOaepSha256,
1396    /// The provider's own envelope, which names no algorithm and follows no
1397    /// published format.
1398    ///
1399    /// AWS KMS is the case to design for. `GenerateDataKey` returns a
1400    /// `CiphertextBlob` of up to 6144 bytes for a 32-byte data key, carrying the
1401    /// key ARN and the encryption context beside the key, and the response names
1402    /// no algorithm at all. Only `Decrypt` against the same service opens it.
1403    ///
1404    /// A standard container cannot carry one. A CMS `KEKRecipientInfo` has to
1405    /// declare a key-encryption algorithm, and declaring `id-aes256-wrap` over
1406    /// a blob that is not an AES key wrap would produce an artifact whose own
1407    /// structure lies about it, which no reader could open and `rite verify`
1408    /// would report as checked. A step that assembles a container refuses this
1409    /// by name instead.
1410    ProviderDefined,
1411}
1412
1413impl fmt::Display for KeyProtection {
1414    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1415        match self {
1416            KeyProtection::AesKeyWrap => write!(f, "AES-KW"),
1417            KeyProtection::RsaOaepSha256 => write!(f, "RSA-OAEP-SHA256"),
1418            KeyProtection::ProviderDefined => write!(f, "a provider-defined envelope"),
1419        }
1420    }
1421}
1422
1423impl KeyProtection {
1424    /// Whether a standard container can record this protection and a standard
1425    /// reader can therefore open the result.
1426    ///
1427    /// Matched exhaustively on purpose: a protection added without answering
1428    /// this would otherwise default into being written into a container that
1429    /// cannot describe it.
1430    #[must_use]
1431    pub fn is_nameable_in_a_container(self) -> bool {
1432        match self {
1433            KeyProtection::AesKeyWrap | KeyProtection::RsaOaepSha256 => true,
1434            KeyProtection::ProviderDefined => false,
1435        }
1436    }
1437}
1438
1439impl std::fmt::Debug for DataKey {
1440    /// Names the shape and never the key. A data key is a secret with a short
1441    /// life, and a debug print is the way such a thing ends up in a log.
1442    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1443        f.debug_struct("DataKey")
1444            .field("plaintext", &format_args!("{} bytes", self.plaintext.len()))
1445            .field("wrapped", &format_args!("{} bytes", self.wrapped.len()))
1446            .field("protection", &self.protection)
1447            .finish()
1448    }
1449}
1450
1451impl DataKey {
1452    /// Pair a fresh data key with the copy its protector can open, and with
1453    /// what protected it.
1454    #[must_use]
1455    pub fn new(
1456        plaintext: zeroize::Zeroizing<Vec<u8>>,
1457        wrapped: Vec<u8>,
1458        protection: KeyProtection,
1459    ) -> Self {
1460        Self {
1461            plaintext,
1462            wrapped,
1463            protection,
1464        }
1465    }
1466
1467    /// The key itself, to encrypt content with.
1468    #[must_use]
1469    pub fn plaintext(&self) -> &[u8] {
1470        &self.plaintext
1471    }
1472
1473    /// The same key, protected. This is what goes into the container.
1474    #[must_use]
1475    pub fn wrapped(&self) -> &[u8] {
1476        &self.wrapped
1477    }
1478
1479    /// What protected it, which is what a container has to declare and what
1480    /// opening it again needs.
1481    #[must_use]
1482    pub fn protection(&self) -> KeyProtection {
1483        self.protection
1484    }
1485}
1486
1487/// The kind of attestation evidence a backend can produce.
1488#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1489#[serde(rename_all = "snake_case")]
1490#[non_exhaustive]
1491pub enum AttestationKind {
1492    /// Vendor cert chain proving on-device generation. Independently verifiable
1493    /// against a public manufacturer root CA.
1494    /// Examples: `YubiKey` F9 attestation, Thales Luna PKC (Public Key Confirmation).
1495    HardwareCertChain,
1496    /// TPM signed quote over PCR values with nonce. Proves platform state.
1497    TpmQuote,
1498    /// PKCS#11 attribute flags: `CKA_ALWAYS_SENSITIVE=true` + `CKA_NEVER_EXTRACTABLE=true`.
1499    /// Trustworthy within the HSM's trust boundary but not independently
1500    /// cryptographically verifiable.
1501    Pkcs11Attributes,
1502}
1503
1504/// Structured attestation evidence with a discriminant kind.
1505///
1506/// The `kind` field tells consumers what they're looking at and how to verify it.
1507#[derive(Debug, Clone, Serialize, Deserialize)]
1508pub struct Attestation {
1509    /// What kind of attestation this is.
1510    pub kind: AttestationKind,
1511    /// DER-encoded certificate chain. Leaf first, then intermediates, then root (if known).
1512    /// Empty for `Pkcs11Attributes` kind.
1513    pub certificates: Vec<Vec<u8>>,
1514    /// Raw signature bytes (if applicable). None for attribute-based attestation.
1515    pub signature: Option<Vec<u8>>,
1516    /// Additional metadata (key attributes, slot info, etc.) for transcript recording.
1517    pub metadata: serde_json::Value,
1518}
1519
1520/// Device information for platform attestation.
1521///
1522/// Collected from the system running the ceremony. Machine IDs are hashed
1523/// (SHA-256) for privacy before inclusion in evidence.
1524#[derive(Debug, Clone, Serialize, Deserialize)]
1525pub struct DeviceInfo {
1526    /// System hostname.
1527    pub hostname: String,
1528    /// Hashed machine ID (SHA-256, format: "sha256:hexhash").
1529    /// None if machine ID not available on this platform.
1530    pub machine_id: Option<String>,
1531    /// CPU model string.
1532    pub cpu_model: Option<String>,
1533    /// Operating system name.
1534    pub os_name: Option<String>,
1535    /// Operating system version.
1536    pub os_version: Option<String>,
1537    /// Kernel version.
1538    pub kernel_version: Option<String>,
1539}
1540
1541/// PIV key slot identifiers.
1542///
1543/// Defined by NIST SP 800-73-5 Part 1, Table 4b ("PIV Card Application Card
1544/// Command Interface: Key References"). These slot assignments are fixed by
1545/// the standard and MUST NOT be changed.
1546///
1547/// # Standard references
1548/// - NIST SP 800-73-5 Part 1, §3.1.2: Key References
1549/// - NIST SP 800-73-5 Part 2, §3.2: GENERAL AUTHENTICATE
1550#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
1551pub enum PivSlot {
1552    /// Slot 9A: PIV Authentication Key.
1553    /// Used for card/cardholder authentication (e.g. system login).
1554    /// PIN required once per session.
1555    /// [NIST SP 800-73-5 Part 1, §3.1.2]
1556    Authentication,
1557
1558    /// Slot 9C: Digital Signature Key.
1559    /// Used for document/code signing. PIN required before EVERY
1560    /// private key operation ("PIN Always" access rule).
1561    /// Data to be signed is hashed off-card.
1562    /// [NIST SP 800-73-5 Part 1, §3.1.2; Part 2, §3.2.4]
1563    Signature,
1564
1565    /// Slot 9D: Key Management Key.
1566    /// Used for key establishment (encryption/decryption).
1567    /// PIN required once per session.
1568    /// [NIST SP 800-73-5 Part 1, §3.1.2]
1569    KeyManagement,
1570
1571    /// Slot 9E: Card Authentication Key.
1572    /// Used for physical access (e.g. PIV-enabled door locks).
1573    /// NO PIN required for private key operations.
1574    /// [NIST SP 800-73-5 Part 1, §3.1.2]
1575    CardAuthentication,
1576
1577    /// Retired Key Management Key slot.
1578    ///
1579    /// Holds a previously used Key Management key for decrypting historical
1580    /// documents. The inner value is an index in `0..=19`, where 0 corresponds
1581    /// to PIV key reference 0x82 and 19 corresponds to 0x95.
1582    ///
1583    /// Construct with [`PivSlot::retired`] to enforce the valid range.
1584    /// [NIST SP 800-73-5 Part 1, §3.1.2]
1585    Retired(u8),
1586}
1587
1588impl PivSlot {
1589    /// Create a retired key management slot from an index in `0..=19`.
1590    ///
1591    /// Returns `None` if `index` is out of the valid range.
1592    /// The 20 retired slots correspond to PIV key references 0x82–0x95.
1593    pub fn retired(index: u8) -> Option<Self> {
1594        (index <= 19).then_some(Self::Retired(index))
1595    }
1596}
1597
1598/// Unified certificate addressing across backend types.
1599///
1600/// PIV addresses certs by slot. PKCS#11 addresses cert objects by `CKA_LABEL` or `CKA_ID`.
1601/// Both are "find me the cert for this key"; the addressing differs by backend type.
1602#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1603#[non_exhaustive]
1604pub enum CertRef {
1605    /// PIV slot identifier (`PivBackend` and `YubikeyBackend`).
1606    PivSlot(PivSlot),
1607    /// Human-readable label string (PKCS#11 `CKA_LABEL`, or software backend).
1608    Label(String),
1609    /// Raw binary identifier (PKCS#11 `CKA_ID`, typically SHA-1 of public key).
1610    RawId(Vec<u8>),
1611}
1612
1613/// PIN access policy for PIV private key operations.
1614///
1615/// Standard PIV defines fixed PIN policies per slot (see `PivSlot` docs).
1616/// `YubiKey` extends this with per-key configurable policies.
1617///
1618/// # Standard references
1619/// - NIST SP 800-73-5 Part 2, §3.2: access rules per key reference
1620/// - Yubico PIV documentation: configurable PIN policies (vendor extension)
1621#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1622#[serde(rename_all = "snake_case")]
1623#[non_exhaustive]
1624pub enum PivPinPolicy {
1625    /// Use the slot's default policy per NIST SP 800-73.
1626    Default,
1627    /// No PIN required (vendor extension, not standard PIV).
1628    Never,
1629    /// PIN verified once per session.
1630    Once,
1631    /// PIN verified before every private key operation ("PIN Always").
1632    Always,
1633}
1634
1635/// Physical touch policy for private key operations.
1636///
1637/// This is a vendor extension (Yubico), not part of the NIST PIV standard.
1638/// Standard PIV cards do not have a touch sensor concept.
1639#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1640#[serde(rename_all = "snake_case")]
1641#[non_exhaustive]
1642pub enum PivTouchPolicy {
1643    /// Use the slot's default touch policy.
1644    Default,
1645    /// No touch required.
1646    Never,
1647    /// Touch required for every operation.
1648    Always,
1649    /// Touch cached for 15 seconds (Yubico-specific).
1650    Cached,
1651}
1652
1653/// Metadata about a populated PIV slot.
1654#[derive(Debug, Clone, Serialize, Deserialize)]
1655pub struct PivSlotInfo {
1656    /// The PIV slot identifier.
1657    pub slot: PivSlot,
1658    /// Key algorithm in this slot (if known).
1659    pub algorithm: Option<KeyAlgorithm>,
1660    /// Whether a certificate is stored in this slot.
1661    /// PIV associates one X.509 certificate with each key slot.
1662    /// [NIST SP 800-73-5 Part 1, §3.2]
1663    pub has_certificate: bool,
1664    /// Origin of the key in this slot.
1665    pub origin: PivKeyOrigin,
1666}
1667
1668/// Origin of a key in a PIV slot.
1669#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1670#[serde(rename_all = "snake_case")]
1671#[non_exhaustive]
1672pub enum PivKeyOrigin {
1673    /// Key was generated on the device (GENERATE ASYMMETRIC KEY PAIR command).
1674    /// [NIST SP 800-73-5 Part 2, §3.1]
1675    Generated,
1676    /// Key was imported from external material.
1677    Imported,
1678    /// Origin unknown (card doesn't report this information).
1679    Unknown,
1680}
1681
1682/// Device identity information for a PIV-compatible smart card.
1683#[derive(Debug, Clone, Serialize, Deserialize)]
1684pub struct PivDeviceInfo {
1685    /// Device serial number (vendor-specific, not standardized by NIST).
1686    pub serial: Option<String>,
1687    /// Firmware version string.
1688    pub firmware_version: Option<String>,
1689    /// Form factor description (e.g. "USB-A", "USB-C", "NFC").
1690    pub form_factor: Option<String>,
1691}
1692
1693/// `YubiKey`-specific slot metadata (vendor extension, not NIST PIV).
1694#[derive(Debug, Clone, Serialize, Deserialize)]
1695pub struct YubikeySlotMetadata {
1696    /// PIN policy configured for this slot.
1697    pub pin_policy: PivPinPolicy,
1698    /// Touch policy configured for this slot.
1699    pub touch_policy: PivTouchPolicy,
1700    /// Origin of the key in this slot.
1701    pub origin: PivKeyOrigin,
1702    /// Public key, if the slot reports one.
1703    pub public_key: Option<PublicKeyDer>,
1704}
1705
1706bitflags::bitflags! {
1707    /// PKCS#11 token capability flags. Maps to the `CKF_*` constants in the PKCS#11 standard.
1708    #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1709    pub struct Pkcs11TokenFlags: u8 {
1710        /// Token requires login before cryptographic operations (`CKF_LOGIN_REQUIRED`).
1711        const LOGIN_REQUIRED      = 0b0000_0001;
1712        /// User PIN has been initialized: `C_InitPIN` has been called (`CKF_USER_PIN_INITIALIZED`).
1713        const USER_PIN_INITIALIZED = 0b0000_0010;
1714        /// Token has been initialized: `C_InitToken` has been called (`CKF_TOKEN_INITIALIZED`).
1715        const TOKEN_INITIALIZED   = 0b0000_0100;
1716        /// Token is write-protected (`CKF_WRITE_PROTECTED`).
1717        const WRITE_PROTECTED     = 0b0000_1000;
1718    }
1719}
1720
1721/// PKCS#11 token information.
1722#[derive(Debug, Clone, Serialize, Deserialize)]
1723pub struct Pkcs11TokenInfo {
1724    /// Token label (padded to 32 bytes in PKCS#11, trimmed here).
1725    pub label: String,
1726    /// Manufacturer ID.
1727    pub manufacturer: String,
1728    /// Token model.
1729    pub model: String,
1730    /// Token serial number.
1731    pub serial: String,
1732    /// Firmware version string.
1733    pub firmware_version: String,
1734    /// Token capability flags.
1735    pub flags: Pkcs11TokenFlags,
1736}
1737
1738/// Opaque PKCS#11 mechanism identifier.
1739///
1740/// An enum would be incomplete (hundreds of `CKM_*` values, plus vendor extensions).
1741/// A string like `"CKM_RSA_PKCS_KEY_PAIR_GEN"` is honest about what we expose.
1742/// Used only for capability checking ("does this token support X?"), not dispatch.
1743#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
1744#[serde(transparent)]
1745pub struct Pkcs11Mechanism(String);
1746
1747impl Pkcs11Mechanism {
1748    /// Create a new `Pkcs11Mechanism` from a `CKM_*` name string.
1749    pub fn new(name: impl Into<String>) -> Self {
1750        Self(name.into())
1751    }
1752
1753    /// Return the mechanism name as a string slice.
1754    pub fn as_str(&self) -> &str {
1755        &self.0
1756    }
1757}
1758
1759impl fmt::Display for Pkcs11Mechanism {
1760    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1761        write!(f, "{}", self.0)
1762    }
1763}
1764
1765/// TPM (Trusted Platform Module) information.
1766#[derive(Debug, Clone, Serialize, Deserialize)]
1767pub struct TpmInfo {
1768    /// TPM specification version (e.g., "2.0").
1769    pub version: String,
1770    /// TPM manufacturer identifier.
1771    pub manufacturer: String,
1772    /// Firmware version (if available).
1773    pub firmware_version: Option<String>,
1774}
1775
1776/// PCR (Platform Configuration Register) value.
1777///
1778/// The `value` field encodes both the algorithm and the hash in the format
1779/// `"algorithm:hexhash"` (e.g., `"sha256:abc123..."`).
1780#[derive(Debug, Clone, Serialize, Deserialize)]
1781pub struct PcrValue {
1782    /// PCR index (0-23 typically).
1783    pub index: u8,
1784    /// PCR value in the format `"algorithm:hexhash"` (e.g., `"sha256:abc123..."`).
1785    pub value: String,
1786}
1787
1788/// Backend configuration entry from a ceremony file.
1789///
1790/// The `provider` field identifies the backend by its self-declared name
1791/// (e.g., `"software"`, `"yubikey"`). The remaining YAML keys are flattened
1792/// into `extra` for backend-specific deserialization.
1793///
1794/// Each backend crate defines its own config struct and deserializes from
1795/// `extra` at startup. This inverts the prior design where the model listed
1796/// concrete backend variants; backends now declare themselves.
1797#[derive(Debug, Clone, Serialize, Deserialize)]
1798pub struct BackendConfig {
1799    /// Backend provider identifier as a lowercase string (e.g., `"software"`).
1800    pub provider: String,
1801    /// Backend-specific configuration key/value pairs.
1802    #[serde(flatten)]
1803    pub extra: serde_json::Value,
1804}
1805
1806#[cfg(test)]
1807mod tests {
1808    use super::*;
1809
1810    #[test]
1811    fn key_algorithm_serde_roundtrip() {
1812        // Serde uses Display strings via `serde(into/try_from)`. These are the canonical
1813        // strings for ceremony YAML `algorithm:` fields and transcripts.
1814        let cases: &[(KeyAlgorithm, &str)] = &[
1815            (KeyAlgorithm::Rsa2048, "\"RSA-2048\""),
1816            (KeyAlgorithm::Rsa4096, "\"RSA-4096\""),
1817            (KeyAlgorithm::EcdsaP256, "\"ECDSA-P256\""),
1818            (KeyAlgorithm::EcdsaP384, "\"ECDSA-P384\""),
1819            (KeyAlgorithm::Ed25519, "\"Ed25519\""),
1820            (KeyAlgorithm::MlDsa44, "\"ML-DSA-44\""),
1821            (KeyAlgorithm::MlDsa65, "\"ML-DSA-65\""),
1822            (KeyAlgorithm::MlDsa87, "\"ML-DSA-87\""),
1823            (KeyAlgorithm::MlKem512, "\"ML-KEM-512\""),
1824            (KeyAlgorithm::MlKem768, "\"ML-KEM-768\""),
1825            (KeyAlgorithm::MlKem1024, "\"ML-KEM-1024\""),
1826            (KeyAlgorithm::Aes128, "\"AES-128\""),
1827            (KeyAlgorithm::Aes256, "\"AES-256\""),
1828        ];
1829        for &(variant, expected) in cases {
1830            let serialized = serde_json::to_string(&variant).unwrap();
1831            assert_eq!(serialized, expected, "serialize {variant:?}");
1832            let deserialized: KeyAlgorithm = serde_json::from_str(expected).unwrap();
1833            assert_eq!(deserialized, variant, "deserialize {expected}");
1834        }
1835    }
1836
1837    #[test]
1838    fn sign_algorithm_serde_roundtrip() {
1839        // Serde uses Display strings via `serde(into/try_from)`. These are the canonical
1840        // strings for ceremony YAML `algorithm:` fields and transcripts.
1841        let cases: &[(SignAlgorithm, &str)] = &[
1842            (SignAlgorithm::RsaPkcs1Sha256, "\"RSA-PKCS1-SHA256\""),
1843            (SignAlgorithm::RsaPssSha256, "\"RSA-PSS-SHA256\""),
1844            (SignAlgorithm::EcdsaSha256, "\"ECDSA-SHA256\""),
1845            (SignAlgorithm::EcdsaSha384, "\"ECDSA-SHA384\""),
1846            (SignAlgorithm::Ed25519, "\"Ed25519\""),
1847            (SignAlgorithm::MlDsa44, "\"ML-DSA-44\""),
1848            (SignAlgorithm::MlDsa65, "\"ML-DSA-65\""),
1849            (SignAlgorithm::MlDsa87, "\"ML-DSA-87\""),
1850        ];
1851        for &(variant, expected) in cases {
1852            let serialized = serde_json::to_string(&variant).unwrap();
1853            assert_eq!(serialized, expected, "serialize {variant:?}");
1854            let deserialized: SignAlgorithm = serde_json::from_str(expected).unwrap();
1855            assert_eq!(deserialized, variant, "deserialize {expected}");
1856        }
1857    }
1858
1859    #[test]
1860    fn sign_algorithm_from_str_rejects_unknown() {
1861        assert!("RSA-PKCS1-SHA512".parse::<SignAlgorithm>().is_err());
1862        assert!("".parse::<SignAlgorithm>().is_err());
1863        assert!(
1864            "ecdsa-sha256".parse::<SignAlgorithm>().is_err(),
1865            "must be case-sensitive"
1866        );
1867        assert!(
1868            "ecdsa_sha256".parse::<SignAlgorithm>().is_err(),
1869            "the snake_case spelling is not accepted"
1870        );
1871    }
1872
1873    /// The default must be a signature algorithm the key can actually perform,
1874    /// or actions that derive one would hand backends an impossible request.
1875    #[test]
1876    fn every_default_sign_algorithm_accepts_its_own_key() {
1877        let signing_keys = [
1878            KeyAlgorithm::Rsa2048,
1879            KeyAlgorithm::Rsa4096,
1880            KeyAlgorithm::EcdsaP256,
1881            KeyAlgorithm::EcdsaP384,
1882            KeyAlgorithm::Ed25519,
1883            KeyAlgorithm::MlDsa44,
1884            KeyAlgorithm::MlDsa65,
1885            KeyAlgorithm::MlDsa87,
1886        ];
1887        for key_algorithm in signing_keys {
1888            let algorithm = key_algorithm
1889                .default_sign_algorithm()
1890                .unwrap_or_else(|| panic!("{key_algorithm} must have a default"));
1891            assert!(
1892                algorithm.accepts_key(key_algorithm),
1893                "{key_algorithm} defaults to {algorithm}, which rejects it"
1894            );
1895        }
1896
1897        // Symmetric keys sign nothing.
1898        assert!(KeyAlgorithm::Aes128.default_sign_algorithm().is_none());
1899        assert!(KeyAlgorithm::Aes256.default_sign_algorithm().is_none());
1900    }
1901
1902    #[test]
1903    fn sign_algorithm_accepts_key_spans_rsa_sizes_and_pins_everything_else() {
1904        // RSA signature schemes are defined for any modulus size, so both key
1905        // sizes are valid. This is what `key_algorithm()` cannot express.
1906        for algorithm in [SignAlgorithm::RsaPkcs1Sha256, SignAlgorithm::RsaPssSha256] {
1907            assert!(algorithm.accepts_key(KeyAlgorithm::Rsa2048));
1908            assert!(algorithm.accepts_key(KeyAlgorithm::Rsa4096));
1909            assert!(!algorithm.accepts_key(KeyAlgorithm::EcdsaP256));
1910        }
1911
1912        // Curves and ML-DSA parameter sets are pinned: a signature algorithm
1913        // names exactly one key algorithm and rejects its neighbours.
1914        assert!(SignAlgorithm::EcdsaSha256.accepts_key(KeyAlgorithm::EcdsaP256));
1915        assert!(!SignAlgorithm::EcdsaSha256.accepts_key(KeyAlgorithm::EcdsaP384));
1916        assert!(SignAlgorithm::MlDsa65.accepts_key(KeyAlgorithm::MlDsa65));
1917        assert!(!SignAlgorithm::MlDsa65.accepts_key(KeyAlgorithm::MlDsa87));
1918
1919        // A signing algorithm never accepts a symmetric key.
1920        assert!(!SignAlgorithm::Ed25519.accepts_key(KeyAlgorithm::Aes256));
1921    }
1922
1923    #[test]
1924    fn wrap_scheme_serde_roundtrip() {
1925        // Serde uses Display strings via `serde(into/try_from)`. These strings
1926        // appear in transcripts.
1927        let cases: &[(WrapScheme, &str)] = &[
1928            (WrapScheme::CmsAes256Gcm, "\"CMS-AES-256-GCM\""),
1929            (WrapScheme::RsaOaepSha256, "\"RSA-OAEP-SHA256\""),
1930            (
1931                WrapScheme::RsaAesKeyWrapSha256,
1932                "\"RSA-AES-KEY-WRAP-SHA256\"",
1933            ),
1934            (WrapScheme::Aes128Kw, "\"AES-128-KW\""),
1935            (WrapScheme::Aes256Kw, "\"AES-256-KW\""),
1936            (WrapScheme::Aes128Kwp, "\"AES-128-KWP\""),
1937            (WrapScheme::Aes256Kwp, "\"AES-256-KWP\""),
1938        ];
1939        for &(variant, expected) in cases {
1940            let serialized = serde_json::to_string(&variant).unwrap();
1941            assert_eq!(serialized, expected, "serialize {variant:?}");
1942            let deserialized: WrapScheme = serde_json::from_str(expected).unwrap();
1943            assert_eq!(deserialized, variant, "deserialize {expected}");
1944        }
1945    }
1946
1947    #[test]
1948    fn wrap_scheme_from_str_rejects_unknown() {
1949        assert!("CMS-RSA-XTS".parse::<WrapScheme>().is_err());
1950        assert!("".parse::<WrapScheme>().is_err());
1951        assert!(
1952            "cms-aes-256-gcm".parse::<WrapScheme>().is_err(),
1953            "must be case-sensitive"
1954        );
1955        assert!(
1956            "CMS-RSA-CBC".parse::<WrapScheme>().is_err(),
1957            "the unauthenticated CBC scheme is gone, not merely undocumented"
1958        );
1959    }
1960
1961    /// A CMS wrap to an RSA recipient, as OpenSSL 3.6 produces it.
1962    fn cms_ktri_gcm() -> WrapDescription {
1963        WrapDescription::new(
1964            RecipientInfoKind::Ktri,
1965            Oid::new(oid::RSA_ENCRYPTION).unwrap(),
1966        )
1967        .with_content_encryption(Oid::new(oid::AES_256_GCM).unwrap())
1968    }
1969
1970    #[test]
1971    fn oid_rejects_values_that_are_not_dotted_decimal() {
1972        assert!(Oid::new("2.16.840.1.101.3.4.1.46").is_ok());
1973        assert!(Oid::new("1.2").is_ok());
1974        assert!(Oid::new("1").is_err(), "a single arc is not an OID");
1975        assert!(
1976            Oid::new("1.2.").is_err(),
1977            "trailing dot leaves an empty arc"
1978        );
1979        assert!(Oid::new("1.2.a").is_err());
1980        assert!(Oid::new("").is_err());
1981    }
1982
1983    #[test]
1984    fn scheme_admits_only_a_matching_description() {
1985        assert!(WrapScheme::CmsAes256Gcm.permits(&cms_ktri_gcm()));
1986
1987        // Same container, CBC content: the scheme fixes GCM, so this is not it.
1988        let cbc = WrapDescription::new(
1989            RecipientInfoKind::Ktri,
1990            Oid::new(oid::RSA_ENCRYPTION).unwrap(),
1991        )
1992        .with_content_encryption(Oid::new(oid::AES_256_CBC).unwrap());
1993        assert!(!WrapScheme::CmsAes256Gcm.permits(&cbc));
1994
1995        // A symmetric KEK is the KEKRI encapsulation, which this scheme does
1996        // produce: the recipient is a key both sides hold rather than one
1997        // reached by public key, and the container is the same.
1998        let kekri = WrapDescription::new(
1999            RecipientInfoKind::Kekri,
2000            Oid::new(oid::AES_256_WRAP).unwrap(),
2001        )
2002        .with_content_encryption(Oid::new(oid::AES_256_GCM).unwrap());
2003        assert!(WrapScheme::CmsAes256Gcm.permits(&kekri));
2004
2005        // The content cipher still has to be authenticated, whichever
2006        // encapsulation carried the key to it.
2007        let kekri_cbc = WrapDescription::new(
2008            RecipientInfoKind::Kekri,
2009            Oid::new(oid::AES_256_WRAP).unwrap(),
2010        )
2011        .with_content_encryption(Oid::new(oid::AES_256_CBC).unwrap());
2012        assert!(!WrapScheme::CmsAes256Gcm.permits(&kekri_cbc));
2013
2014        // A password recipient is not one Rite writes, and admitting it would
2015        // mean a passphrase had silently stood in for a key.
2016        let pwri = WrapDescription::new(
2017            RecipientInfoKind::Pwri,
2018            Oid::new(oid::AES_256_WRAP).unwrap(),
2019        )
2020        .with_content_encryption(Oid::new(oid::AES_256_GCM).unwrap());
2021        assert!(!WrapScheme::CmsAes256Gcm.permits(&pwri));
2022    }
2023
2024    #[test]
2025    fn wrapped_key_rejects_a_scheme_its_description_contradicts() {
2026        let coherent = WrappedKey::new(WrapScheme::CmsAes256Gcm, cms_ktri_gcm(), vec![1, 2, 3]);
2027        assert!(coherent.is_ok());
2028
2029        let unauthenticated = WrapDescription::new(
2030            RecipientInfoKind::Ktri,
2031            Oid::new(oid::RSA_ENCRYPTION).unwrap(),
2032        )
2033        .with_content_encryption(Oid::new(oid::AES_256_CBC).unwrap());
2034        let mislabelled = WrappedKey::new(WrapScheme::CmsAes256Gcm, unauthenticated, vec![1, 2, 3]);
2035        assert!(
2036            mislabelled.is_err(),
2037            "bytes must not carry a label they contradict"
2038        );
2039    }
2040
2041    #[test]
2042    fn wrapped_key_recheck_survives_deserialization() {
2043        let wrapped =
2044            WrappedKey::new(WrapScheme::CmsAes256Gcm, cms_ktri_gcm(), vec![9, 9]).unwrap();
2045        let json = serde_json::to_string(&wrapped).unwrap();
2046        let back: WrappedKey = serde_json::from_str(&json).unwrap();
2047        assert_eq!(back.scheme(), WrapScheme::CmsAes256Gcm);
2048        assert_eq!(back.data(), &[9, 9]);
2049
2050        // An artifact edited to disagree with itself does not deserialize.
2051        let tampered = json.replace(oid::AES_256_GCM, oid::AES_256_CBC);
2052        assert!(serde_json::from_str::<WrappedKey>(&tampered).is_err());
2053    }
2054
2055    #[test]
2056    fn gcm_content_is_the_authenticated_case() {
2057        assert!(cms_ktri_gcm().content_is_authenticated());
2058        let cbc = WrapDescription::new(
2059            RecipientInfoKind::Ktri,
2060            Oid::new(oid::RSA_ENCRYPTION).unwrap(),
2061        )
2062        .with_content_encryption(Oid::new(oid::AES_256_CBC).unwrap());
2063        assert!(!cbc.content_is_authenticated());
2064    }
2065
2066    #[test]
2067    fn piv_slot_retired_enforces_valid_range() {
2068        // Indices 0–19 map to PIV key references 0x82–0x95.
2069        // An out-of-range index would send an invalid reference to PIV hardware.
2070        assert!(PivSlot::retired(0).is_some(), "index 0 must be valid");
2071        assert!(
2072            PivSlot::retired(19).is_some(),
2073            "index 19 must be valid (last slot 0x95)"
2074        );
2075        assert!(
2076            PivSlot::retired(20).is_none(),
2077            "index 20 must be rejected (out of range)"
2078        );
2079        assert!(
2080            PivSlot::retired(255).is_none(),
2081            "index 255 must be rejected"
2082        );
2083    }
2084
2085    #[test]
2086    fn key_policy_default_is_secure() {
2087        // The default is documented as the most secure configuration for ceremony signing keys.
2088        // A regression here (e.g., extractable=true) is a silent security downgrade.
2089        let policy = KeyPolicy::default();
2090        assert!(
2091            policy.persistent,
2092            "ceremony keys must persist across sessions"
2093        );
2094        assert!(policy.sensitive, "ceremony keys must be marked sensitive");
2095        assert!(
2096            !policy.extractable,
2097            "ceremony keys must not be extractable by default"
2098        );
2099        assert!(policy.usages.contains(KeyUsages::SIGN));
2100        assert!(policy.usages.contains(KeyUsages::VERIFY));
2101        assert!(
2102            !policy.usages.contains(KeyUsages::ENCRYPT),
2103            "signing-only default must not permit encryption"
2104        );
2105        assert!(
2106            !policy.usages.contains(KeyUsages::WRAP),
2107            "signing-only default must not permit wrapping"
2108        );
2109    }
2110}