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