cellos_projector/
event_decode.rs1use std::collections::HashMap;
18use std::path::PathBuf;
19use std::sync::Arc;
20
21use anyhow::{anyhow, Result};
22use cellos_core::{
23 load_trust_verify_keys_file, verify_signed_event_envelope, CloudEventV1, SignedEventEnvelopeV1,
24};
25use ed25519_dalek::VerifyingKey;
26
27#[derive(Clone, Default)]
31pub struct EventVerifierConfig {
32 pub verifying_keys: Arc<HashMap<String, VerifyingKey>>,
35 pub hmac_keys: Arc<HashMap<String, Vec<u8>>>,
39 pub require_signed: bool,
42}
43
44impl EventVerifierConfig {
45 pub fn from_env() -> Result<Self> {
57 let require_signed = std::env::var("CELLOS_EVENT_REQUIRE_SIGNED")
58 .map(|v| {
59 let t = v.trim().to_ascii_lowercase();
60 matches!(t.as_str(), "1" | "true" | "yes" | "on")
61 })
62 .unwrap_or(false);
63
64 let verifying_keys = match std::env::var("CELLOS_EVENT_VERIFY_KEYS_PATH") {
65 Ok(p) if !p.trim().is_empty() => {
66 let path = PathBuf::from(p.trim());
67 let keys = load_trust_verify_keys_file(&path).map_err(|e| {
68 anyhow!(
69 "CELLOS_EVENT_VERIFY_KEYS_PATH={}: load failed: {e}",
70 path.display()
71 )
72 })?;
73 Arc::new(keys)
74 }
75 _ => Arc::new(HashMap::new()),
76 };
77
78 Ok(Self {
79 verifying_keys,
80 hmac_keys: Arc::new(HashMap::new()),
81 require_signed,
82 })
83 }
84
85 pub fn has_keys(&self) -> bool {
87 !self.verifying_keys.is_empty() || !self.hmac_keys.is_empty()
88 }
89}
90
91pub fn decode_event(bytes: &[u8], cfg: &EventVerifierConfig) -> Result<CloudEventV1> {
101 if let Ok(envelope) = serde_json::from_slice::<SignedEventEnvelopeV1>(bytes) {
104 if cfg.has_keys() {
109 verify_signed_event_envelope(&envelope, &cfg.verifying_keys, &cfg.hmac_keys)
110 .map_err(|e| anyhow!("signed event envelope verify failed: {e}"))?;
111 }
112 return Ok(envelope.event);
113 }
114
115 if cfg.require_signed {
117 return Err(anyhow!(
118 "CELLOS_EVENT_REQUIRE_SIGNED=1 but payload is not a SignedEventEnvelopeV1"
119 ));
120 }
121 let event: CloudEventV1 = serde_json::from_slice(bytes)
122 .map_err(|e| anyhow!("payload is neither a signed envelope nor a CloudEventV1: {e}"))?;
123 Ok(event)
124}
125
126#[cfg(test)]
127mod tests {
128 use super::*;
129 use cellos_core::sign_event_ed25519;
130 use ed25519_dalek::SigningKey;
131
132 fn signing_key(seed: u8) -> SigningKey {
133 SigningKey::from_bytes(&[seed; 32])
134 }
135
136 fn sample_event(id: &str) -> CloudEventV1 {
137 CloudEventV1 {
138 specversion: "1.0".into(),
139 id: id.into(),
140 source: "/test".into(),
141 ty: "dev.cellos.events.cell.lifecycle.v1.started".into(),
142 datacontenttype: Some("application/json".into()),
143 data: Some(serde_json::json!({"cellId": "c-1", "specId": "s-1"})),
144 time: None,
145 traceparent: None,
146 }
147 }
148
149 #[test]
150 fn raw_event_accepted_in_permissive_mode() {
151 let cfg = EventVerifierConfig::default();
152 let event = sample_event("e1");
153 let bytes = serde_json::to_vec(&event).unwrap();
154 let decoded = decode_event(&bytes, &cfg).expect("permissive accepts raw");
155 assert_eq!(decoded.id, "e1");
156 }
157
158 #[test]
159 fn raw_event_rejected_when_require_signed() {
160 let cfg = EventVerifierConfig {
161 require_signed: true,
162 ..Default::default()
163 };
164 let event = sample_event("e1");
165 let bytes = serde_json::to_vec(&event).unwrap();
166 let err = decode_event(&bytes, &cfg).expect_err("require_signed rejects raw");
167 assert!(format!("{err}").contains("REQUIRE_SIGNED"));
168 }
169
170 #[test]
171 fn signed_envelope_verifies_against_keyring() {
172 let signer = signing_key(7);
173 let event = sample_event("e1");
174 let envelope = sign_event_ed25519(&event, "kid-7", &signer).unwrap();
175 let bytes = serde_json::to_vec(&envelope).unwrap();
176
177 let mut keys = HashMap::new();
178 keys.insert("kid-7".to_string(), signer.verifying_key());
179 let cfg = EventVerifierConfig {
180 verifying_keys: Arc::new(keys),
181 hmac_keys: Arc::new(HashMap::new()),
182 require_signed: true,
183 };
184
185 let decoded = decode_event(&bytes, &cfg).expect("verifies");
186 assert_eq!(decoded.id, "e1");
187 }
188
189 #[test]
190 fn signed_envelope_with_tampered_event_rejected() {
191 let signer = signing_key(7);
192 let event = sample_event("e1");
193 let mut envelope = sign_event_ed25519(&event, "kid-7", &signer).unwrap();
194 envelope.event.id = "tampered".into();
195 let bytes = serde_json::to_vec(&envelope).unwrap();
196
197 let mut keys = HashMap::new();
198 keys.insert("kid-7".to_string(), signer.verifying_key());
199 let cfg = EventVerifierConfig {
200 verifying_keys: Arc::new(keys),
201 hmac_keys: Arc::new(HashMap::new()),
202 require_signed: false,
203 };
204
205 let err = decode_event(&bytes, &cfg).expect_err("tampered must be rejected");
206 assert!(format!("{err}").contains("verify failed"));
207 }
208
209 #[test]
210 fn signed_envelope_unknown_kid_rejected() {
211 let signer = signing_key(7);
212 let event = sample_event("e1");
213 let envelope = sign_event_ed25519(&event, "kid-unknown", &signer).unwrap();
214 let bytes = serde_json::to_vec(&envelope).unwrap();
215
216 let mut keys = HashMap::new();
217 keys.insert("kid-other".to_string(), signing_key(11).verifying_key());
218 let cfg = EventVerifierConfig {
219 verifying_keys: Arc::new(keys),
220 hmac_keys: Arc::new(HashMap::new()),
221 require_signed: false,
222 };
223
224 let err = decode_event(&bytes, &cfg).expect_err("unknown kid must be rejected");
225 let msg = format!("{err}");
226 assert!(
227 msg.contains("verify failed") || msg.contains("unknown"),
228 "got: {msg}"
229 );
230 }
231
232 #[test]
233 fn signed_envelope_accepted_without_keyring() {
234 let signer = signing_key(7);
236 let event = sample_event("e1");
237 let envelope = sign_event_ed25519(&event, "kid-7", &signer).unwrap();
238 let bytes = serde_json::to_vec(&envelope).unwrap();
239
240 let cfg = EventVerifierConfig::default();
241 let decoded = decode_event(&bytes, &cfg).expect("no-keys is transparent unwrap");
242 assert_eq!(decoded.id, "e1");
243 }
244}