Skip to main content

klieo_ops/
source_identity_registry.rs

1//! Source-agent identity → Ed25519 verifying-key registry for Handoff
2//! envelope verification.
3//!
4//! Spec § 1.7 + § 2.6: receivers MUST resolve `HandoffEnvelope::source_identity`
5//! against a trusted key registry before adopting. v0.4 closes this gap
6//! by making the registry a required dependency of `KvHandoff` — there is
7//! no fail-open path.
8
9use crate::key_registry::{load_ed25519_yaml, KeyRegistryError, StaticEd25519KeyRegistry};
10use crate::types::AgentId;
11use ed25519_dalek::VerifyingKey;
12use std::collections::HashMap;
13use std::path::Path;
14use thiserror::Error;
15
16/// Errors raised by registry loading + lookup.
17#[derive(Debug, Error)]
18#[non_exhaustive]
19pub enum SourceIdentityRegistryError {
20    /// Wraps the underlying key-registry error.
21    #[error(transparent)]
22    Load(#[from] KeyRegistryError),
23}
24
25/// Lookup the expected verifying key for a source agent.
26pub trait SourceIdentityRegistry: Send + Sync {
27    /// Resolve the trusted Ed25519 verifying key for `agent`, or `None`
28    /// if the agent is not registered.
29    fn lookup(&self, agent: &AgentId) -> Option<VerifyingKey>;
30}
31
32/// In-memory registry, loadable from a YAML file at construction.
33///
34/// YAML shape:
35/// ```yaml
36/// claims-triage: "BASE64_ED25519_PUBKEY"
37/// underwriter-v1: "BASE64_ED25519_PUBKEY"
38/// ```
39pub struct StaticSourceIdentityRegistry {
40    inner: StaticEd25519KeyRegistry<AgentId>,
41}
42
43impl StaticSourceIdentityRegistry {
44    /// Build from an in-memory map.
45    #[must_use]
46    pub fn from_map(keys: HashMap<AgentId, VerifyingKey>) -> Self {
47        Self {
48            inner: StaticEd25519KeyRegistry::from_map(keys),
49        }
50    }
51
52    /// Load from a YAML file.
53    pub fn from_yaml(path: impl AsRef<Path>) -> Result<Self, SourceIdentityRegistryError> {
54        let keys = load_ed25519_yaml(path, AgentId)?;
55        Ok(Self {
56            inner: StaticEd25519KeyRegistry::from_map(keys),
57        })
58    }
59}
60
61impl SourceIdentityRegistry for StaticSourceIdentityRegistry {
62    fn lookup(&self, agent: &AgentId) -> Option<VerifyingKey> {
63        use crate::key_registry::Ed25519KeyRegistry as _;
64        self.inner.lookup(agent)
65    }
66}
67
68#[cfg(test)]
69mod tests {
70    use super::*;
71    use ed25519_dalek::SigningKey;
72
73    #[test]
74    fn round_trip_via_yaml() {
75        let sk = SigningKey::from_bytes(&[5u8; 32]);
76        let pk = sk.verifying_key();
77        use base64::Engine as _;
78        let b64 = base64::engine::general_purpose::STANDARD.encode(pk.to_bytes());
79        let yaml = format!("claims-triage: \"{b64}\"\n");
80        let path = std::env::temp_dir().join("klieo-test-source-identity.yaml");
81        std::fs::write(&path, yaml).unwrap();
82        let reg = StaticSourceIdentityRegistry::from_yaml(&path).expect("load");
83        let resolved = reg.lookup(&AgentId("claims-triage".into())).expect("found");
84        assert_eq!(resolved.to_bytes(), pk.to_bytes());
85    }
86
87    #[test]
88    fn missing_agent_returns_none() {
89        let reg = StaticSourceIdentityRegistry::from_map(HashMap::new());
90        assert!(reg.lookup(&AgentId("ghost".into())).is_none());
91    }
92}