use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[derive(Default)]
pub enum MrvbMode {
#[default]
ClassicalOnly,
#[cfg(feature = "pqc")]
PqcOnly,
#[cfg(feature = "hybrid")]
Hybrid,
}
impl std::fmt::Display for MrvbMode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
MrvbMode::ClassicalOnly => write!(f, "ClassicalOnly"),
#[cfg(feature = "pqc")]
MrvbMode::PqcOnly => write!(f, "PqcOnly"),
#[cfg(feature = "hybrid")]
MrvbMode::Hybrid => write!(f, "Hybrid"),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MrvbConfig {
pub mode: MrvbMode,
pub keyset_id: String,
}
impl Default for MrvbConfig {
fn default() -> Self {
Self {
mode: MrvbMode::default(),
keyset_id: "default".to_string(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ClassicalKeyPair {
pub key_id: String,
pub algorithm: String,
#[serde(with = "base64_bytes")]
pub private_key: Vec<u8>,
#[serde(with = "base64_bytes")]
pub public_key: Vec<u8>,
}
#[cfg(feature = "pqc")]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PqcKeyPair {
pub key_id: String,
pub algorithm: String,
#[serde(with = "base64_bytes")]
pub private_key: Vec<u8>,
#[serde(with = "base64_bytes")]
pub public_key: Vec<u8>,
}
#[cfg(not(feature = "pqc"))]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PqcKeyPair {
pub key_id: String,
pub algorithm: String,
#[serde(with = "base64_bytes")]
pub private_key: Vec<u8>,
#[serde(with = "base64_bytes")]
pub public_key: Vec<u8>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct KeyPairSet {
pub keyset_id: String,
pub classical: Option<ClassicalKeyPair>,
pub pqc: Option<PqcKeyPair>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub created_at: Option<chrono::DateTime<chrono::Utc>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub rotate_after: Option<chrono::DateTime<chrono::Utc>>,
}
impl KeyPairSet {
pub fn supports_mode(&self, mode: MrvbMode) -> bool {
match mode {
MrvbMode::ClassicalOnly => self.classical.is_some(),
#[cfg(feature = "pqc")]
MrvbMode::PqcOnly => self.pqc.is_some(),
#[cfg(feature = "hybrid")]
MrvbMode::Hybrid => self.classical.is_some() && self.pqc.is_some(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HybridSignature {
#[serde(
default,
skip_serializing_if = "Option::is_none",
with = "option_base64_bytes"
)]
pub classical_sig: Option<Vec<u8>>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
with = "option_base64_bytes"
)]
pub pqc_sig: Option<Vec<u8>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub alg_classical: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub alg_pqc: Option<String>,
pub keyset_id: String,
pub mode: MrvbMode,
}
impl HybridSignature {
pub fn has_any_signature(&self) -> bool {
self.classical_sig.is_some() || self.pqc_sig.is_some()
}
#[cfg(feature = "hybrid")]
pub fn has_both_signatures(&self) -> bool {
self.classical_sig.is_some() && self.pqc_sig.is_some()
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct AssertionClaims {
pub session_id: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub user_id: Option<String>,
pub rail: String,
pub verification_level: String,
#[serde(with = "chrono::serde::ts_seconds")]
pub issued_at: chrono::DateTime<chrono::Utc>,
#[serde(with = "chrono::serde::ts_seconds")]
pub expires_at: chrono::DateTime<chrono::Utc>,
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
pub metadata: HashMap<String, serde_json::Value>,
}
impl AssertionClaims {
pub fn is_expired(&self) -> bool {
chrono::Utc::now() > self.expires_at
}
pub fn is_valid(&self) -> bool {
let now = chrono::Utc::now();
now >= self.issued_at && now <= self.expires_at
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SignedAssertion {
pub claims: AssertionClaims,
pub signature: HybridSignature,
#[serde(default = "default_version")]
pub version: String,
}
fn default_version() -> String {
"1.0".to_string()
}
impl SignedAssertion {
pub fn to_json(&self) -> Result<String, serde_json::Error> {
serde_json::to_string(self)
}
pub fn from_json(json: &str) -> Result<Self, serde_json::Error> {
serde_json::from_str(json)
}
pub fn to_compact_json(&self) -> Result<String, serde_json::Error> {
serde_json::to_string(self)
}
}
mod base64_bytes {
use base64::{engine::general_purpose::STANDARD as BASE64, Engine as _};
use serde::{Deserialize, Deserializer, Serializer};
pub fn serialize<S>(bytes: &Vec<u8>, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&BASE64.encode(bytes))
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<u8>, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
BASE64.decode(&s).map_err(serde::de::Error::custom)
}
}
mod option_base64_bytes {
use base64::{engine::general_purpose::STANDARD as BASE64, Engine as _};
use serde::{Deserialize, Deserializer, Serializer};
pub fn serialize<S>(bytes: &Option<Vec<u8>>, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match bytes {
Some(b) => serializer.serialize_some(&BASE64.encode(b)),
None => serializer.serialize_none(),
}
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<Vec<u8>>, D::Error>
where
D: Deserializer<'de>,
{
let opt: Option<String> = Option::deserialize(deserializer)?;
match opt {
Some(s) => BASE64
.decode(&s)
.map(Some)
.map_err(serde::de::Error::custom),
None => Ok(None),
}
}
}