Skip to main content

uvb_mrvb/
types.rs

1//! Core types for MRVB assertion signing and verification.
2
3use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5
6/// MRVB signing mode configuration.
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
8#[serde(rename_all = "snake_case")]
9#[derive(Default)]
10pub enum MrvbMode {
11    /// Classical algorithms only (Ed25519) - maximum compatibility
12    #[default]
13    ClassicalOnly,
14    /// Post-quantum only (Dilithium3) - for future-proofing / internal systems
15    #[cfg(feature = "pqc")]
16    PqcOnly,
17    /// Hybrid: both classical + PQC signatures - recommended for production
18    #[cfg(feature = "hybrid")]
19    Hybrid,
20}
21
22impl std::fmt::Display for MrvbMode {
23    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
24        match self {
25            MrvbMode::ClassicalOnly => write!(f, "ClassicalOnly"),
26            #[cfg(feature = "pqc")]
27            MrvbMode::PqcOnly => write!(f, "PqcOnly"),
28            #[cfg(feature = "hybrid")]
29            MrvbMode::Hybrid => write!(f, "Hybrid"),
30        }
31    }
32}
33
34/// MRVB configuration for signing operations.
35#[derive(Debug, Clone, Serialize, Deserialize)]
36pub struct MrvbConfig {
37    /// Current signing mode
38    pub mode: MrvbMode,
39    /// Active keyset ID (for key rotation tracking)
40    pub keyset_id: String,
41}
42
43impl Default for MrvbConfig {
44    fn default() -> Self {
45        Self {
46            mode: MrvbMode::default(),
47            keyset_id: "default".to_string(),
48        }
49    }
50}
51
52/// Classical keypair (e.g., Ed25519).
53#[derive(Debug, Clone, Serialize, Deserialize)]
54pub struct ClassicalKeyPair {
55    pub key_id: String,
56    /// Algorithm identifier (e.g., "ed25519")
57    pub algorithm: String,
58    /// Private key bytes, base64 encoded for JSON serialization
59    #[serde(with = "base64_bytes")]
60    pub private_key: Vec<u8>,
61    /// Public key bytes, base64 encoded for JSON serialization
62    #[serde(with = "base64_bytes")]
63    pub public_key: Vec<u8>,
64}
65
66/// Post-quantum keypair (e.g., Dilithium3).
67#[cfg(feature = "pqc")]
68#[derive(Debug, Clone, Serialize, Deserialize)]
69pub struct PqcKeyPair {
70    pub key_id: String,
71    /// Algorithm identifier (e.g., "dilithium3")
72    pub algorithm: String,
73    /// Private key bytes, base64 encoded
74    #[serde(with = "base64_bytes")]
75    pub private_key: Vec<u8>,
76    /// Public key bytes, base64 encoded
77    #[serde(with = "base64_bytes")]
78    pub public_key: Vec<u8>,
79}
80
81/// Stub for non-pqc builds
82#[cfg(not(feature = "pqc"))]
83#[derive(Debug, Clone, Serialize, Deserialize)]
84pub struct PqcKeyPair {
85    pub key_id: String,
86    pub algorithm: String,
87    #[serde(with = "base64_bytes")]
88    pub private_key: Vec<u8>,
89    #[serde(with = "base64_bytes")]
90    pub public_key: Vec<u8>,
91}
92
93/// Combined keyset for hybrid signing/verification.
94///
95/// Matches the design from the MRVB+KMS pack.
96#[derive(Debug, Clone, Serialize, Deserialize)]
97pub struct KeyPairSet {
98    pub keyset_id: String,
99    /// Classical keypair (required for ClassicalOnly and Hybrid modes)
100    pub classical: Option<ClassicalKeyPair>,
101    /// PQC keypair (required for PqcOnly and Hybrid modes)
102    pub pqc: Option<PqcKeyPair>,
103    /// Creation timestamp
104    #[serde(default, skip_serializing_if = "Option::is_none")]
105    pub created_at: Option<chrono::DateTime<chrono::Utc>>,
106    /// Rotation timestamp (when this keyset should be rotated)
107    #[serde(default, skip_serializing_if = "Option::is_none")]
108    pub rotate_after: Option<chrono::DateTime<chrono::Utc>>,
109}
110
111impl KeyPairSet {
112    /// Check if this keyset supports the given signing mode.
113    pub fn supports_mode(&self, mode: MrvbMode) -> bool {
114        match mode {
115            MrvbMode::ClassicalOnly => self.classical.is_some(),
116            #[cfg(feature = "pqc")]
117            MrvbMode::PqcOnly => self.pqc.is_some(),
118            #[cfg(feature = "hybrid")]
119            MrvbMode::Hybrid => self.classical.is_some() && self.pqc.is_some(),
120        }
121    }
122}
123
124/// Result of a hybrid signature operation.
125#[derive(Debug, Clone, Serialize, Deserialize)]
126pub struct HybridSignature {
127    /// Classical signature bytes (base64 encoded)
128    #[serde(
129        default,
130        skip_serializing_if = "Option::is_none",
131        with = "option_base64_bytes"
132    )]
133    pub classical_sig: Option<Vec<u8>>,
134    /// PQC signature bytes (base64 encoded)
135    #[serde(
136        default,
137        skip_serializing_if = "Option::is_none",
138        with = "option_base64_bytes"
139    )]
140    pub pqc_sig: Option<Vec<u8>>,
141    /// Classical algorithm used
142    #[serde(default, skip_serializing_if = "Option::is_none")]
143    pub alg_classical: Option<String>,
144    /// PQC algorithm used
145    #[serde(default, skip_serializing_if = "Option::is_none")]
146    pub alg_pqc: Option<String>,
147    /// Keyset ID used for signing
148    pub keyset_id: String,
149    /// Signing mode used
150    pub mode: MrvbMode,
151}
152
153impl HybridSignature {
154    /// Check if this signature has at least one valid component.
155    pub fn has_any_signature(&self) -> bool {
156        self.classical_sig.is_some() || self.pqc_sig.is_some()
157    }
158
159    /// Check if this signature has both components (for hybrid mode).
160    #[cfg(feature = "hybrid")]
161    pub fn has_both_signatures(&self) -> bool {
162        self.classical_sig.is_some() && self.pqc_sig.is_some()
163    }
164}
165
166/// Assertion claims structure for MRVB verification tokens.
167///
168/// This is similar to JWT claims but specialized for MRVB verification flows.
169#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
170pub struct AssertionClaims {
171    /// Unique session identifier
172    pub session_id: String,
173    /// User identifier (optional, may be omitted for privacy)
174    #[serde(default, skip_serializing_if = "Option::is_none")]
175    pub user_id: Option<String>,
176    /// Rail used for verification (e.g., "email", "sms", "webauthn")
177    pub rail: String,
178    /// Verification confidence level (e.g., "low", "medium", "high")
179    pub verification_level: String,
180    /// Issuance timestamp
181    #[serde(with = "chrono::serde::ts_seconds")]
182    pub issued_at: chrono::DateTime<chrono::Utc>,
183    /// Expiration timestamp
184    #[serde(with = "chrono::serde::ts_seconds")]
185    pub expires_at: chrono::DateTime<chrono::Utc>,
186    /// Additional metadata (extensible)
187    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
188    pub metadata: HashMap<String, serde_json::Value>,
189}
190
191impl AssertionClaims {
192    /// Check if the assertion has expired.
193    pub fn is_expired(&self) -> bool {
194        chrono::Utc::now() > self.expires_at
195    }
196
197    /// Check if the assertion is valid (not expired and issued in the past).
198    pub fn is_valid(&self) -> bool {
199        let now = chrono::Utc::now();
200        now >= self.issued_at && now <= self.expires_at
201    }
202}
203
204/// A signed MRVB assertion token.
205///
206/// This combines the assertion claims with a cryptographic signature.
207#[derive(Debug, Clone, Serialize, Deserialize)]
208pub struct SignedAssertion {
209    /// The assertion claims (payload)
210    pub claims: AssertionClaims,
211    /// The cryptographic signature
212    pub signature: HybridSignature,
213    /// Token format version (for future compatibility)
214    #[serde(default = "default_version")]
215    pub version: String,
216}
217
218fn default_version() -> String {
219    "1.0".to_string()
220}
221
222impl SignedAssertion {
223    /// Serialize to JSON for transmission or storage.
224    pub fn to_json(&self) -> Result<String, serde_json::Error> {
225        serde_json::to_string(self)
226    }
227
228    /// Deserialize from JSON.
229    pub fn from_json(json: &str) -> Result<Self, serde_json::Error> {
230        serde_json::from_str(json)
231    }
232
233    /// Serialize to compact JSON (no whitespace).
234    pub fn to_compact_json(&self) -> Result<String, serde_json::Error> {
235        serde_json::to_string(self)
236    }
237}
238
239// ============================================================================
240// Base64 serde helpers for clean JSON serialization of byte arrays
241// ============================================================================
242
243mod base64_bytes {
244    use base64::{engine::general_purpose::STANDARD as BASE64, Engine as _};
245    use serde::{Deserialize, Deserializer, Serializer};
246
247    pub fn serialize<S>(bytes: &Vec<u8>, serializer: S) -> Result<S::Ok, S::Error>
248    where
249        S: Serializer,
250    {
251        serializer.serialize_str(&BASE64.encode(bytes))
252    }
253
254    pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<u8>, D::Error>
255    where
256        D: Deserializer<'de>,
257    {
258        let s = String::deserialize(deserializer)?;
259        BASE64.decode(&s).map_err(serde::de::Error::custom)
260    }
261}
262
263mod option_base64_bytes {
264    use base64::{engine::general_purpose::STANDARD as BASE64, Engine as _};
265    use serde::{Deserialize, Deserializer, Serializer};
266
267    pub fn serialize<S>(bytes: &Option<Vec<u8>>, serializer: S) -> Result<S::Ok, S::Error>
268    where
269        S: Serializer,
270    {
271        match bytes {
272            Some(b) => serializer.serialize_some(&BASE64.encode(b)),
273            None => serializer.serialize_none(),
274        }
275    }
276
277    pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<Vec<u8>>, D::Error>
278    where
279        D: Deserializer<'de>,
280    {
281        let opt: Option<String> = Option::deserialize(deserializer)?;
282        match opt {
283            Some(s) => BASE64
284                .decode(&s)
285                .map(Some)
286                .map_err(serde::de::Error::custom),
287            None => Ok(None),
288        }
289    }
290}