use crate::error::WSError;
use serde::{Deserialize, Serialize};
pub const TRUST_BUNDLE_FORMAT_VERSION: u8 = 1;
pub const MAX_GRACE_PERIOD_SECONDS: u64 = 365 * 86400;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TrustBundle {
pub format_version: u8,
pub version: u32,
#[serde(default)]
pub bundle_id: String,
pub created_at: u64,
pub validity: ValidityPeriod,
pub certificate_authorities: Vec<CertificateAuthority>,
pub transparency_logs: Vec<TransparencyLog>,
#[serde(default)]
pub revocations: Vec<String>,
}
impl TrustBundle {
pub fn new(version: u32, validity_days: u32) -> Self {
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_secs();
Self {
format_version: TRUST_BUNDLE_FORMAT_VERSION,
version,
bundle_id: String::new(),
created_at: now,
validity: ValidityPeriod {
not_before: now,
not_after: now + (validity_days as u64 * 86400),
grace_period_seconds: 30 * 86400, },
certificate_authorities: Vec::new(),
transparency_logs: Vec::new(),
revocations: Vec::new(),
}
}
pub fn set_grace_period(&mut self, seconds: u64) -> u64 {
let capped = seconds.min(MAX_GRACE_PERIOD_SECONDS);
if capped < seconds {
log::warn!(
"Grace period capped from {} to {} seconds (max {} days)",
seconds,
capped,
MAX_GRACE_PERIOD_SECONDS / 86400
);
}
self.validity.grace_period_seconds = capped;
capped
}
pub fn add_certificate_authority(&mut self, ca: CertificateAuthority) {
self.certificate_authorities.push(ca);
}
pub fn add_transparency_log(&mut self, log: TransparencyLog) {
self.transparency_logs.push(log);
}
pub fn add_revocation(&mut self, fingerprint: String) {
if !self.revocations.contains(&fingerprint) {
self.revocations.push(fingerprint);
}
}
pub fn is_valid(&self, current_time: u64) -> bool {
current_time >= self.validity.not_before && current_time <= self.validity.not_after
}
pub fn is_in_grace_period(&self, current_time: u64) -> bool {
let capped_grace = self
.validity
.grace_period_seconds
.min(MAX_GRACE_PERIOD_SECONDS);
let grace_end = self
.validity
.not_after
.checked_add(capped_grace)
.unwrap_or(u64::MAX);
current_time > self.validity.not_after && current_time <= grace_end
}
pub fn is_revoked(&self, fingerprint: &str) -> bool {
self.revocations.iter().any(|r| r == fingerprint)
}
pub fn compute_bundle_id(&mut self) -> Result<(), WSError> {
let old_id = std::mem::take(&mut self.bundle_id);
let canonical = serde_json::to_vec(self)
.map_err(|e| WSError::InternalError(format!("Failed to serialize bundle: {}", e)))?;
let hash = hmac_sha256::Hash::hash(&canonical);
self.bundle_id = hex::encode(hash);
if self.bundle_id.is_empty() {
self.bundle_id = old_id;
}
Ok(())
}
pub fn to_json(&self) -> Result<Vec<u8>, WSError> {
serde_json::to_vec_pretty(self)
.map_err(|e| WSError::InternalError(format!("Failed to serialize bundle: {}", e)))
}
pub fn from_json(data: &[u8]) -> Result<Self, WSError> {
serde_json::from_slice(data)
.map_err(|e| WSError::InternalError(format!("Failed to parse bundle: {}", e)))
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ValidityPeriod {
pub not_before: u64,
pub not_after: u64,
#[serde(default)]
pub grace_period_seconds: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CertificateAuthority {
pub name: String,
pub uri: String,
pub certificates_pem: Vec<String>,
pub valid_for: ValidityPeriod,
}
impl CertificateAuthority {
pub fn new(name: &str, uri: &str, pem_certs: Vec<String>, validity_days: u32) -> Self {
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_secs();
Self {
name: name.to_string(),
uri: uri.to_string(),
certificates_pem: pem_certs,
valid_for: ValidityPeriod {
not_before: now,
not_after: now + (validity_days as u64 * 86400),
grace_period_seconds: 0,
},
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TransparencyLog {
pub base_url: String,
pub hash_algorithm: String,
pub public_key_pem: String,
pub log_id: String,
pub valid_for: ValidityPeriod,
}
impl TransparencyLog {
pub fn new(base_url: &str, public_key_pem: &str, validity_days: u32) -> Result<Self, WSError> {
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_secs();
let log_id = Self::compute_log_id(public_key_pem)?;
Ok(Self {
base_url: base_url.to_string(),
hash_algorithm: "sha256".to_string(),
public_key_pem: public_key_pem.to_string(),
log_id,
valid_for: ValidityPeriod {
not_before: now,
not_after: now + (validity_days as u64 * 86400),
grace_period_seconds: 0,
},
})
}
fn compute_log_id(pem: &str) -> Result<String, WSError> {
let der = pem
.lines()
.filter(|line| !line.starts_with("-----"))
.collect::<String>();
let der_bytes = base64::Engine::decode(
&base64::engine::general_purpose::STANDARD,
&der,
)
.map_err(|e| WSError::InternalError(format!("Invalid PEM encoding: {}", e)))?;
let hash = hmac_sha256::Hash::hash(&der_bytes);
Ok(hex::encode(hash))
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SignedTrustBundle {
pub bundle: TrustBundle,
pub signature: BundleSignature,
}
impl SignedTrustBundle {
pub fn sign(bundle: TrustBundle, signing_key: &[u8]) -> Result<Self, WSError> {
let bundle_bytes = bundle.to_json()?;
let signature = BundleSignature::sign(&bundle_bytes, signing_key)?;
Ok(Self { bundle, signature })
}
pub fn verify(&self, verifier_key: &[u8]) -> Result<(), WSError> {
let bundle_bytes = self.bundle.to_json()?;
self.signature.verify(&bundle_bytes, verifier_key)
}
pub fn to_json(&self) -> Result<Vec<u8>, WSError> {
serde_json::to_vec_pretty(self)
.map_err(|e| WSError::InternalError(format!("Failed to serialize signed bundle: {}", e)))
}
pub fn from_json(data: &[u8]) -> Result<Self, WSError> {
serde_json::from_slice(data)
.map_err(|e| WSError::InternalError(format!("Failed to parse signed bundle: {}", e)))
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BundleSignature {
pub key_id: String,
pub algorithm: SignatureAlgorithm,
pub signature: String,
}
impl BundleSignature {
pub fn sign(data: &[u8], secret_key: &[u8]) -> Result<Self, WSError> {
use ed25519_compact::{KeyPair, Seed};
let seed = if secret_key.len() == 32 {
Seed::from_slice(secret_key)
.map_err(|e| WSError::CryptoError(e))?
} else if secret_key.len() == 64 {
Seed::from_slice(&secret_key[..32])
.map_err(|e| WSError::CryptoError(e))?
} else {
return Err(WSError::InvalidArgument);
};
let keypair = KeyPair::from_seed(seed);
let pk_bytes = keypair.pk.as_ref();
let key_hash = hmac_sha256::Hash::hash(pk_bytes);
let key_id = hex::encode(&key_hash[..4]);
let sig = keypair.sk.sign(data, None);
let signature = base64::Engine::encode(
&base64::engine::general_purpose::STANDARD,
sig.as_ref(),
);
Ok(Self {
key_id,
algorithm: SignatureAlgorithm::Ed25519,
signature,
})
}
pub fn verify(&self, data: &[u8], public_key: &[u8]) -> Result<(), WSError> {
use ed25519_compact::{PublicKey, Signature};
match self.algorithm {
SignatureAlgorithm::Ed25519 => {
let pk = PublicKey::from_slice(public_key)
.map_err(|e| WSError::CryptoError(e))?;
let sig_bytes = base64::Engine::decode(
&base64::engine::general_purpose::STANDARD,
&self.signature,
)
.map_err(|_| WSError::InvalidArgument)?;
let sig = Signature::from_slice(&sig_bytes)
.map_err(|e| WSError::CryptoError(e))?;
pk.verify(data, &sig)
.map_err(|e| WSError::CryptoError(e))
}
_ => Err(WSError::UnsupportedAlgorithm(format!("{:?}", self.algorithm))),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum SignatureAlgorithm {
Ed25519,
EcdsaP256Sha256,
EcdsaP384Sha384,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_trust_bundle_creation() {
let bundle = TrustBundle::new(1, 365);
assert_eq!(bundle.format_version, TRUST_BUNDLE_FORMAT_VERSION);
assert_eq!(bundle.version, 1);
assert!(bundle.is_valid(bundle.created_at));
}
#[test]
fn test_trust_bundle_validity() {
let mut bundle = TrustBundle::new(1, 365);
bundle.validity.not_before = 1000;
bundle.validity.not_after = 2000;
bundle.validity.grace_period_seconds = 500;
assert!(!bundle.is_valid(500)); assert!(bundle.is_valid(1500)); assert!(!bundle.is_valid(2500));
assert!(!bundle.is_in_grace_period(1500)); assert!(bundle.is_in_grace_period(2100)); assert!(!bundle.is_in_grace_period(2600)); }
#[test]
fn test_trust_bundle_revocation() {
let mut bundle = TrustBundle::new(1, 365);
bundle.add_revocation("abc123".to_string());
assert!(bundle.is_revoked("abc123"));
assert!(!bundle.is_revoked("def456"));
}
#[test]
fn test_trust_bundle_json_roundtrip() {
let mut bundle = TrustBundle::new(1, 365);
bundle.add_certificate_authority(CertificateAuthority::new(
"Test CA",
"https://example.com",
vec!["-----BEGIN CERTIFICATE-----\ntest\n-----END CERTIFICATE-----".to_string()],
365,
));
let json = bundle.to_json().unwrap();
let parsed = TrustBundle::from_json(&json).unwrap();
assert_eq!(parsed.version, bundle.version);
assert_eq!(parsed.certificate_authorities.len(), 1);
}
#[test]
fn test_signed_bundle_roundtrip() {
use ed25519_compact::KeyPair;
let keypair = KeyPair::generate();
let seed = keypair.sk.seed();
let secret_key = seed.as_ref();
let bundle = TrustBundle::new(1, 365);
let signed = SignedTrustBundle::sign(bundle, secret_key).unwrap();
let public_key = keypair.pk.as_ref();
assert!(signed.verify(public_key).is_ok());
let json = signed.to_json().unwrap();
let parsed = SignedTrustBundle::from_json(&json).unwrap();
assert!(parsed.verify(public_key).is_ok());
}
#[test]
fn test_signed_bundle_wrong_key_fails() {
use ed25519_compact::KeyPair;
let keypair1 = KeyPair::generate();
let keypair2 = KeyPair::generate();
let bundle = TrustBundle::new(1, 365);
let seed1 = keypair1.sk.seed();
let signed = SignedTrustBundle::sign(bundle, seed1.as_ref()).unwrap();
let result = signed.verify(keypair2.pk.as_ref());
assert!(result.is_err());
}
}