Skip to main content

vex_runtime/audit/
verify.rs

1use super::vep::{
2    AuthoritySegment, EvidenceCapsuleV0, IdentitySegment, IntentSegment, RequestCommitment,
3    WitnessSegment,
4};
5use thiserror::Error;
6
7#[derive(Error, Debug)]
8pub enum VerifierError {
9    #[error("Invalid magic: expected VEP, got {0:?}")]
10    InvalidMagic([u8; 3]),
11    #[error("Unsupported version: expected {0}, got {1}")]
12    UnsupportedVersion(u8, u8),
13    #[error("Header size mismatch")]
14    HeaderTooSmall,
15    #[error("JSON parsing error: {0}")]
16    Json(#[from] serde_json::Error),
17    #[error("Integrity error: {0}")]
18    Integrity(String),
19    #[error("Cryptographic error: {0}")]
20    Crypto(String),
21}
22
23/// The VepVerifier performs independent validation of Verifiable Evidence Packets.
24pub struct VepVerifier;
25
26impl VepVerifier {
27    /// Verify a raw VEP binary blob.
28    pub fn verify_binary(
29        data: &[u8],
30        public_key: Option<&[u8]>,
31    ) -> Result<EvidenceCapsuleV0, VerifierError> {
32        use vex_core::vep::VepPacket;
33
34        // 1. Use Core VEP Packet for TLV extraction
35        let packet = VepPacket::new(data).map_err(|e| VerifierError::Integrity(e.to_string()))?;
36
37        // 2. Perform cryptographic verification if key provided
38        if let Some(pk) = public_key {
39            packet
40                .verify(pk)
41                .map_err(|e| VerifierError::Crypto(e.to_string()))?;
42        }
43
44        // 3. Reconstruct the High-Level Capsule (Core)
45        let core_capsule = packet
46            .to_capsule()
47            .map_err(|e| VerifierError::Integrity(e.to_string()))?;
48
49        // 4. Map back to Runtime VEP Capsule (V0)
50        let intent = match &core_capsule.intent {
51            vex_core::segment::IntentData::Transparent {
52                request_sha256,
53                confidence,
54                capabilities,
55                magpie_source,
56                continuation_token: _,
57                metadata,
58            } => IntentSegment {
59                request_sha256: request_sha256.clone(),
60                confidence: *confidence,
61                capabilities: capabilities.clone(),
62                magpie_source: magpie_source.clone(),
63                circuit_id: None, // V0 verifier assumes transparent/unproven for now
64                intent_data: Some(core_capsule.intent.clone()),
65                metadata: vex_core::segment::SchemaValue(metadata.0.clone()),
66            },
67            vex_core::segment::IntentData::Shadow { .. } => {
68                return Err(VerifierError::Integrity(
69                    "SHADOW_INTENT_NOT_SUPPORTED_IN_V0".to_string(),
70                ))
71            }
72        };
73
74        let authority = AuthoritySegment {
75            capsule_id: core_capsule.authority.capsule_id,
76            outcome: core_capsule.authority.outcome,
77            reason_code: core_capsule.authority.reason_code,
78            trace_root: core_capsule.authority.trace_root,
79            nonce: core_capsule.authority.nonce,
80            escalation_id: core_capsule.authority.escalation_id,
81            binding_status: core_capsule.authority.binding_status,
82            continuation_token: core_capsule.authority.continuation_token,
83            authority_class: core_capsule.authority.authority_class,
84            gate_sensors: core_capsule.authority.gate_sensors,
85            metadata: core_capsule.authority.metadata,
86        };
87
88        let identity = IdentitySegment {
89            aid: core_capsule.identity.aid,
90            identity_type: core_capsule.identity.identity_type,
91            pcrs: core_capsule.identity.pcrs,
92            metadata: core_capsule.identity.metadata,
93        };
94
95        let witness = WitnessSegment {
96            chora_node_id: core_capsule.witness.chora_node_id,
97            receipt_hash: core_capsule.witness.receipt_hash,
98            timestamp: core_capsule.witness.timestamp,
99            metadata: core_capsule.witness.metadata,
100        };
101
102        let request_commitment = core_capsule.request_commitment.map(|rc| RequestCommitment {
103            canonicalization: rc.canonicalization,
104            payload_sha256: rc.payload_sha256,
105            payload_encoding: rc.payload_encoding,
106        });
107
108        let mut v0 =
109            EvidenceCapsuleV0::new(intent, authority, identity, witness, request_commitment)
110                .map_err(|e| VerifierError::Integrity(e.to_string()))?;
111
112        v0.crypto.signature_b64 = core_capsule.crypto.signature_b64;
113
114        Ok(v0)
115    }
116
117    /// Re-run the formal verification on the bundled Magpie AST.
118    pub async fn reverify_formal_intent(&self, capsule: &EvidenceCapsuleV0) -> Result<(), String> {
119        let source = capsule
120            .intent
121            .magpie_source
122            .as_ref()
123            .ok_or("VEP_MISSING_SOURCE: No bundled Magpie AST found")?;
124
125        let tmp_path = std::env::temp_dir().join(format!("verify_{}.mp", capsule.capsule_id));
126        tokio::fs::write(&tmp_path, source)
127            .await
128            .map_err(|e| format!("IO_ERROR: {}", e))?;
129
130        struct Cleanup(std::path::PathBuf);
131        impl Drop for Cleanup {
132            fn drop(&mut self) {
133                let _ = std::fs::remove_file(&self.0);
134            }
135        }
136        let _cleanup = Cleanup(tmp_path.clone());
137
138        use tokio::process::Command;
139        let mut cmd = Command::new(crate::utils::find_magpie_binary());
140        cmd.arg("--output")
141            .arg("json")
142            .arg("--entry")
143            .arg(&tmp_path)
144            .arg("parse");
145
146        let output = cmd
147            .output()
148            .await
149            .map_err(|e| format!("MAGPIE_EXEC_ERROR: {}", e))?;
150
151        if output.status.success() {
152            Ok(())
153        } else {
154            let stderr = String::from_utf8_lossy(&output.stderr);
155            Err(format!("FORMAL_VERIFICATION_FAILED: {}", stderr))
156        }
157    }
158}
159
160#[cfg(test)]
161mod tests {
162    use super::super::vep::{AuthoritySegment, IdentitySegment, IntentSegment, WitnessSegment};
163    use super::*;
164    use ed25519_dalek::SigningKey;
165    use rand::rngs::OsRng;
166
167    #[test]
168    fn test_vep_end_to_end_verification() {
169        let mut csprng = OsRng;
170        let signing_key = SigningKey::generate(&mut csprng);
171        let verifying_key = signing_key.verifying_key();
172
173        let intent = IntentSegment {
174            request_sha256: "aabbcc".to_string(),
175            confidence: 0.9,
176            capabilities: vec!["test".to_string()],
177            magpie_source: None,
178            circuit_id: None,
179            intent_data: None,
180            metadata: vex_core::segment::SchemaValue(serde_json::Value::Null),
181        };
182        let authority = AuthoritySegment {
183            capsule_id: "test-capsule".to_string(),
184            outcome: "ALLOW".to_string(),
185            reason_code: "OK".to_string(),
186            trace_root: "tr".to_string(),
187            nonce: "42".to_string(),
188            escalation_id: None,
189            binding_status: None,
190            continuation_token: None,
191            authority_class: Some("ALLOW_PATH".to_string()),
192            gate_sensors: vex_core::segment::SchemaValue(serde_json::Value::Null),
193            metadata: vex_core::segment::SchemaValue(serde_json::Value::Null),
194        };
195        let identity = IdentitySegment {
196            aid: hex::encode([0u8; 32]), // Mock AID
197            identity_type: "mock".to_string(),
198            pcrs: None,
199            metadata: vex_core::segment::SchemaValue(serde_json::Value::Null),
200        };
201        let witness = WitnessSegment {
202            chora_node_id: "node1".to_string(),
203            receipt_hash: "rh".to_string(),
204            timestamp: 1710396000,
205            metadata: vex_core::segment::SchemaValue(serde_json::Value::Null),
206        };
207
208        let mut capsule =
209            EvidenceCapsuleV0::new(intent, authority, identity, witness, None).unwrap();
210        capsule.sign(&signing_key).unwrap();
211
212        let binary = capsule.to_vep_binary().unwrap();
213
214        // Verify with correct key
215        let verified = VepVerifier::verify_binary(&binary, Some(verifying_key.as_bytes())).unwrap();
216        assert_eq!(verified.capsule_id, "test-capsule");
217
218        // Verify with no key (Integrity only)
219        let integrity_only = VepVerifier::verify_binary(&binary, None).unwrap();
220        assert_eq!(integrity_only.capsule_id, "test-capsule");
221
222        // Tamper test (change a byte in the header root)
223        let mut tampered = binary.clone();
224        tampered[40] ^= 0xFF;
225        let err = VepVerifier::verify_binary(&tampered, None).unwrap_err();
226        assert!(matches!(err, VerifierError::Integrity(_)));
227    }
228
229    #[tokio::test]
230    async fn test_audit_store_vep_integration() {
231        use crate::audit::vep::{AuthoritySegment, IdentitySegment, IntentSegment, WitnessSegment};
232        use std::sync::Arc;
233        use vex_core::audit::AuditEventType;
234        use vex_persist::backend::MemoryBackend;
235        use vex_persist::AuditStore;
236
237        let backend = Arc::new(MemoryBackend::new());
238        let store = AuditStore::new(backend);
239        let tenant = "handshake-test";
240
241        let mut csprng = OsRng;
242        let signing_key = SigningKey::generate(&mut csprng);
243
244        // 1. Create a VEP
245        let intent = IntentSegment {
246            request_sha256: "deadbeef".to_string(),
247            confidence: 1.0,
248            capabilities: vec!["audit".to_string()],
249            magpie_source: None,
250            circuit_id: None,
251            intent_data: None,
252            metadata: vex_core::segment::SchemaValue(serde_json::Value::Null),
253        };
254        let authority = AuthoritySegment {
255            capsule_id: "capsule-xyz-789".to_string(),
256            outcome: "ALLOW".to_string(),
257            reason_code: "VERIFIED".to_string(),
258            trace_root: "tr".to_string(),
259            nonce: "101".to_string(),
260            escalation_id: None,
261            binding_status: None,
262            continuation_token: None,
263            authority_class: Some("ALLOW_PATH".to_string()),
264            gate_sensors: vex_core::segment::SchemaValue(serde_json::Value::Null),
265            metadata: vex_core::segment::SchemaValue(serde_json::Value::Null),
266        };
267        let identity = IdentitySegment {
268            aid: hex::encode([1u8; 32]),
269            identity_type: "hardware".to_string(),
270            pcrs: None,
271            metadata: vex_core::segment::SchemaValue(serde_json::Value::Null),
272        };
273        let witness = WitnessSegment {
274            chora_node_id: "nodeB".to_string(),
275            receipt_hash: "receiptB".to_string(),
276            timestamp: 1710396000,
277            metadata: vex_core::segment::SchemaValue(serde_json::Value::Null),
278        };
279
280        let mut capsule =
281            EvidenceCapsuleV0::new(intent, authority, identity, witness, None).unwrap();
282        capsule.sign(&signing_key).unwrap();
283        let binary = capsule.to_vep_binary().unwrap();
284
285        // 2. Log to AuditStore
286        store
287            .log(
288                tenant,
289                AuditEventType::GateDecision,
290                vex_core::audit::ActorType::System("verifier".to_string()),
291                None,
292                serde_json::json!({
293                    "authority": {
294                        "capsule_id": "capsule-xyz-789",
295                        "outcome": "ALLOW",
296                        "reason_code": "VERIFIED",
297                        "nonce": "101"
298                    }
299                }),
300                None,
301                Some("receiptB".to_string()),
302                Some(binary.clone()),
303            )
304            .await
305            .unwrap();
306
307        // 3. Auditor Handshake: Retrieve by Capsule ID
308        let retrieved_blob = store
309            .get_vep_by_capsule_id(tenant, "capsule-xyz-789")
310            .await
311            .unwrap();
312        assert!(retrieved_blob.is_some());
313        let blob = retrieved_blob.unwrap();
314        assert_eq!(blob, binary);
315
316        // 4. Auditor Handshake: Cryptographic Verification
317        let result =
318            VepVerifier::verify_binary(&blob, Some(signing_key.verifying_key().as_bytes()))
319                .unwrap();
320        assert_eq!(result.capsule_id, "capsule-xyz-789");
321        assert_eq!(result.authority.nonce, "101");
322    }
323}