b8ce58ddf0
aura-crypto: X25519 + ML-KEM-768 (FIPS 203) hybrid KEM, HKDF-SHA256 session key derivation, ChaCha20-Poly1305 AeadSession with counter nonces; genuine NIST ACVP ML-KEM-768 KAT (decapsulation vector). 16 tests green, clippy clean. aura-pki: self-signed CA, server/client cert issuance (rcgen 0.14), mutual X.509 chain verification via rustls-webpki, CRL revocation. 8 tests green. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
89 lines
2.6 KiB
Rust
89 lines
2.6 KiB
Rust
//! Trust material storage: a simple v1 CRL (set of revoked identifiers).
|
|
//!
|
|
//! The Aura v1 revocation list is deliberately minimal: a set of opaque
|
|
//! identifier strings. An identifier is either a certificate serial number
|
|
//! (lowercase hex, no separators) or a client id / Common Name. A certificate
|
|
//! is rejected if any of those identifiers is present in the set.
|
|
|
|
use std::collections::BTreeSet;
|
|
use std::fs;
|
|
use std::path::Path;
|
|
|
|
use anyhow::Context;
|
|
|
|
/// A set of revoked certificate identifiers (serials and/or client ids).
|
|
#[derive(Debug, Default, Clone, PartialEq, Eq)]
|
|
pub struct CrlStore {
|
|
revoked: BTreeSet<String>,
|
|
}
|
|
|
|
impl CrlStore {
|
|
/// Create an empty CRL.
|
|
pub fn new() -> Self {
|
|
Self::default()
|
|
}
|
|
|
|
/// Add a single revoked identifier (serial hex or client id).
|
|
pub fn revoke(&mut self, id: impl Into<String>) {
|
|
self.revoked.insert(id.into());
|
|
}
|
|
|
|
/// True if `id` is in the revocation set.
|
|
pub fn contains(&self, id: &str) -> bool {
|
|
self.revoked.contains(id)
|
|
}
|
|
|
|
/// True if no certificates are revoked.
|
|
pub fn is_empty(&self) -> bool {
|
|
self.revoked.is_empty()
|
|
}
|
|
|
|
/// Number of revoked identifiers.
|
|
pub fn len(&self) -> usize {
|
|
self.revoked.len()
|
|
}
|
|
|
|
/// Iterate over the revoked identifiers (sorted).
|
|
pub fn iter(&self) -> impl Iterator<Item = &str> {
|
|
self.revoked.iter().map(String::as_str)
|
|
}
|
|
|
|
/// Persist the CRL, one identifier per line.
|
|
pub fn save(&self, path: &Path) -> anyhow::Result<()> {
|
|
let mut body = String::new();
|
|
for id in &self.revoked {
|
|
body.push_str(id);
|
|
body.push('\n');
|
|
}
|
|
fs::write(path, body).with_context(|| format!("writing CRL to {}", path.display()))?;
|
|
Ok(())
|
|
}
|
|
|
|
/// Load a CRL written by [`CrlStore::save`] (one identifier per line; blank
|
|
/// lines and `#` comments are ignored).
|
|
pub fn load(path: &Path) -> anyhow::Result<Self> {
|
|
let text = fs::read_to_string(path)
|
|
.with_context(|| format!("reading CRL from {}", path.display()))?;
|
|
Ok(Self::from_iter(
|
|
text.lines()
|
|
.map(str::trim)
|
|
.filter(|l| !l.is_empty() && !l.starts_with('#'))
|
|
.map(str::to_string),
|
|
))
|
|
}
|
|
}
|
|
|
|
impl FromIterator<String> for CrlStore {
|
|
fn from_iter<T: IntoIterator<Item = String>>(iter: T) -> Self {
|
|
Self {
|
|
revoked: iter.into_iter().collect(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Extend<String> for CrlStore {
|
|
fn extend<T: IntoIterator<Item = String>>(&mut self, iter: T) {
|
|
self.revoked.extend(iter);
|
|
}
|
|
}
|