use std::collections::{HashMap, HashSet};
use crate::crypto::{Hash, PublicKey};
use super::attestation::{AgentAttestation, AttestationError};
pub struct AttestationRegistry {
attestations: HashMap<[u8; 32], AgentAttestation>,
authorities: HashSet<[u8; 32]>,
revocations: HashSet<Hash>,
revocation_reasons: HashMap<Hash, String>,
}
impl AttestationRegistry {
pub fn new() -> Self {
Self {
attestations: HashMap::new(),
authorities: HashSet::new(),
revocations: HashSet::new(),
revocation_reasons: HashMap::new(),
}
}
pub fn add_authority(&mut self, authority: &PublicKey) {
self.authorities.insert(authority.as_bytes());
}
pub fn remove_authority(&mut self, authority: &PublicKey) {
self.authorities.remove(&authority.as_bytes());
}
pub fn is_trusted_authority(&self, authority: &PublicKey) -> bool {
self.authorities.contains(&authority.as_bytes())
}
pub fn authority_count(&self) -> usize {
self.authorities.len()
}
pub fn register(&mut self, attestation: AgentAttestation) -> Result<(), AttestationError> {
attestation
.verify_signature()
.map_err(|_| AttestationError::InvalidSignature)?;
if !self.is_trusted_authority(attestation.authority()) {
return Err(AttestationError::UntrustedAuthority);
}
let now = chrono::Utc::now().timestamp_millis();
if !attestation.is_valid_at(now) {
return Err(AttestationError::Expired);
}
let key = attestation.agent_id().as_bytes();
self.attestations.insert(key, attestation);
Ok(())
}
pub fn verify(
&self,
agent_id: &PublicKey,
action_time: i64,
) -> Result<&AgentAttestation, AttestationError> {
let key = agent_id.as_bytes();
let attestation = self
.attestations
.get(&key)
.ok_or(AttestationError::NotFound)?;
let attestation_hash = attestation.hash();
if self.revocations.contains(&attestation_hash) {
return Err(AttestationError::Revoked);
}
if !attestation.is_valid_at(action_time) {
return Err(AttestationError::Expired);
}
Ok(attestation)
}
pub fn revoke(&mut self, attestation_hash: Hash, reason: String) {
self.revocations.insert(attestation_hash);
self.revocation_reasons.insert(attestation_hash, reason);
}
pub fn is_revoked(&self, attestation_hash: &Hash) -> bool {
self.revocations.contains(attestation_hash)
}
pub fn revocation_reason(&self, attestation_hash: &Hash) -> Option<&String> {
self.revocation_reasons.get(attestation_hash)
}
pub fn get(&self, agent_id: &PublicKey) -> Option<&AgentAttestation> {
let key = agent_id.as_bytes();
self.attestations.get(&key)
}
pub fn remove(&mut self, agent_id: &PublicKey) -> Option<AgentAttestation> {
let key = agent_id.as_bytes();
self.attestations.remove(&key)
}
pub fn attestation_count(&self) -> usize {
self.attestations.len()
}
pub fn revocation_count(&self) -> usize {
self.revocations.len()
}
}
impl Default for AttestationRegistry {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::crypto::{hash, SecretKey};
use std::time::Duration;
use super::super::attestation::{AgentAttestation, RuntimeAttestation};
fn create_attestation(
agent: &SecretKey,
authority: &SecretKey,
attested_at: i64,
validity_secs: u64,
) -> AgentAttestation {
AgentAttestation::builder()
.agent_id(agent.public_key())
.code_hash(hash(b"code"))
.config_hash(hash(b"config"))
.prompt_hash(hash(b"prompt"))
.runtime(RuntimeAttestation::new("test-v1", hash(b"runtime")))
.attested_at(attested_at)
.validity_period(Duration::from_secs(validity_secs))
.sign(authority)
.unwrap()
}
#[test]
fn register_stores_attestation() {
let authority = SecretKey::generate();
let agent = SecretKey::generate();
let mut registry = AttestationRegistry::new();
registry.add_authority(&authority.public_key());
let now = chrono::Utc::now().timestamp_millis();
let attestation = create_attestation(&agent, &authority, now, 3600);
registry.register(attestation).unwrap();
assert!(registry.get(&agent.public_key()).is_some());
assert_eq!(registry.attestation_count(), 1);
}
#[test]
fn register_rejects_untrusted_authority() {
let authority = SecretKey::generate();
let agent = SecretKey::generate();
let mut registry = AttestationRegistry::new();
let now = chrono::Utc::now().timestamp_millis();
let attestation = create_attestation(&agent, &authority, now, 3600);
let result = registry.register(attestation);
assert!(matches!(result, Err(AttestationError::UntrustedAuthority)));
}
#[test]
fn register_rejects_expired_attestation() {
let authority = SecretKey::generate();
let agent = SecretKey::generate();
let mut registry = AttestationRegistry::new();
registry.add_authority(&authority.public_key());
let past = chrono::Utc::now().timestamp_millis() - 10000;
let attestation = create_attestation(&agent, &authority, past, 1);
let result = registry.register(attestation);
assert!(matches!(result, Err(AttestationError::Expired)));
}
#[test]
fn verify_returns_attestation_if_valid() {
let authority = SecretKey::generate();
let agent = SecretKey::generate();
let mut registry = AttestationRegistry::new();
registry.add_authority(&authority.public_key());
let now = chrono::Utc::now().timestamp_millis();
let attestation = create_attestation(&agent, &authority, now, 3600);
registry.register(attestation).unwrap();
let result = registry.verify(&agent.public_key(), now + 1000);
assert!(result.is_ok());
assert_eq!(result.unwrap().agent_id(), &agent.public_key());
}
#[test]
fn verify_fails_if_not_registered() {
let authority = SecretKey::generate();
let agent = SecretKey::generate();
let mut registry = AttestationRegistry::new();
registry.add_authority(&authority.public_key());
let now = chrono::Utc::now().timestamp_millis();
let result = registry.verify(&agent.public_key(), now);
assert!(matches!(result, Err(AttestationError::NotFound)));
}
#[test]
fn verify_fails_if_expired_at_action_time() {
let authority = SecretKey::generate();
let agent = SecretKey::generate();
let mut registry = AttestationRegistry::new();
registry.add_authority(&authority.public_key());
let now = chrono::Utc::now().timestamp_millis();
let attestation = create_attestation(&agent, &authority, now, 60);
registry.register(attestation).unwrap();
let result = registry.verify(&agent.public_key(), now + 70 * 1000);
assert!(matches!(result, Err(AttestationError::Expired)));
}
#[test]
fn verify_fails_if_revoked() {
let authority = SecretKey::generate();
let agent = SecretKey::generate();
let mut registry = AttestationRegistry::new();
registry.add_authority(&authority.public_key());
let now = chrono::Utc::now().timestamp_millis();
let attestation = create_attestation(&agent, &authority, now, 3600);
let attestation_hash = attestation.hash();
registry.register(attestation).unwrap();
registry.revoke(attestation_hash, "Security incident".to_string());
let result = registry.verify(&agent.public_key(), now + 1000);
assert!(matches!(result, Err(AttestationError::Revoked)));
}
#[test]
fn revoke_adds_to_revocation_list() {
let mut registry = AttestationRegistry::new();
let hash = hash(b"attestation-data");
registry.revoke(hash, "Test revocation".to_string());
assert!(registry.is_revoked(&hash));
assert_eq!(registry.revocation_count(), 1);
}
#[test]
fn revoke_makes_verify_fail() {
let authority = SecretKey::generate();
let agent = SecretKey::generate();
let mut registry = AttestationRegistry::new();
registry.add_authority(&authority.public_key());
let now = chrono::Utc::now().timestamp_millis();
let attestation = create_attestation(&agent, &authority, now, 3600);
let attestation_hash = attestation.hash();
registry.register(attestation).unwrap();
assert!(registry.verify(&agent.public_key(), now + 1000).is_ok());
registry.revoke(attestation_hash, "Compromised".to_string());
let result = registry.verify(&agent.public_key(), now + 2000);
assert!(matches!(result, Err(AttestationError::Revoked)));
}
#[test]
fn revoke_is_permanent() {
let mut registry = AttestationRegistry::new();
let hash = hash(b"attestation-data");
registry.revoke(hash, "First revocation".to_string());
assert!(registry.is_revoked(&hash));
assert!(registry.is_revoked(&hash));
}
#[test]
fn revocation_reason_recorded() {
let mut registry = AttestationRegistry::new();
let hash = hash(b"attestation-data");
let reason = "Security vulnerability discovered".to_string();
registry.revoke(hash, reason.clone());
assert_eq!(registry.revocation_reason(&hash), Some(&reason));
}
#[test]
fn add_and_remove_authority() {
let authority = SecretKey::generate();
let mut registry = AttestationRegistry::new();
assert!(!registry.is_trusted_authority(&authority.public_key()));
assert_eq!(registry.authority_count(), 0);
registry.add_authority(&authority.public_key());
assert!(registry.is_trusted_authority(&authority.public_key()));
assert_eq!(registry.authority_count(), 1);
registry.remove_authority(&authority.public_key());
assert!(!registry.is_trusted_authority(&authority.public_key()));
assert_eq!(registry.authority_count(), 0);
}
#[test]
fn remove_attestation() {
let authority = SecretKey::generate();
let agent = SecretKey::generate();
let mut registry = AttestationRegistry::new();
registry.add_authority(&authority.public_key());
let now = chrono::Utc::now().timestamp_millis();
let attestation = create_attestation(&agent, &authority, now, 3600);
registry.register(attestation).unwrap();
assert_eq!(registry.attestation_count(), 1);
let removed = registry.remove(&agent.public_key());
assert!(removed.is_some());
assert_eq!(registry.attestation_count(), 0);
}
}