Skip to main content

flux_verify_api/
verify_middleware.rs

1use std::collections::HashSet;
2use tracing::{info, warn, error};
3use crate::signing::{self, Signature, SigningError};
4
5/// Middleware that verifies bytecode signatures before allowing execution.
6pub struct VerificationMiddleware {
7    /// Set of trusted Ed25519 public keys (32 bytes each).
8    trusted_keys: HashSet<[u8; 32]>,
9    /// If true, reject all unsigned bytecode. If false, log a warning but allow.
10    strict: bool,
11}
12
13/// Result of a verification attempt.
14#[derive(Debug)]
15pub struct VerificationResult {
16    pub allowed: bool,
17    pub reason: String,
18    pub key_index: Option<usize>,
19}
20
21impl VerificationMiddleware {
22    /// Create a new middleware with at least one trusted public key.
23    pub fn new(trusted_keys: Vec<[u8; 32]>) -> Self {
24        assert!(!trusted_keys.is_empty(), "at least one trusted key required");
25        Self {
26            trusted_keys: trusted_keys.into_iter().collect(),
27            strict: true,
28        }
29    }
30
31    /// Toggle strict mode. When strict (default), unsigned bytecode is rejected.
32    pub fn with_strict(mut self, strict: bool) -> Self {
33        self.strict = strict;
34        self
35    }
36
37    /// Add a new trusted public key (for key rotation).
38    pub fn add_trusted_key(&mut self, public_key: [u8; 32]) {
39        info!("adding trusted public key: {:?}", hex::encode(public_key));
40        self.trusted_keys.insert(public_key);
41    }
42
43    /// Remove a trusted public key (e.g., revoke a compromised key).
44    pub fn remove_trusted_key(&mut self, public_key: &[u8; 32]) {
45        info!("removing trusted public key: {:?}", hex::encode(public_key));
46        self.trusted_keys.remove(public_key);
47    }
48
49    /// Number of currently trusted keys.
50    pub fn trusted_key_count(&self) -> usize {
51        self.trusted_keys.len()
52    }
53
54    /// Verify bytecode against all trusted public keys.
55    ///
56    /// Returns `Ok(VerificationResult)` if any trusted key validates the signature.
57    /// Returns an error result if none match or if the bytecode is tampered.
58    pub fn verify(&self, bytecode: &[u8], signature: &Signature) -> VerificationResult {
59        let fingerprint_hex = hex::encode(signature.fingerprint);
60        info!(
61            fingerprint = %fingerprint_hex,
62            timestamp = signature.timestamp,
63            bytecode_len = bytecode.len(),
64            "verification attempt started"
65        );
66
67        // Try each trusted key.
68        for (idx, pk) in self.trusted_keys.iter().enumerate() {
69            match signing::verify_bytecode(bytecode, signature, pk) {
70                Ok(()) => {
71                    info!(
72                        fingerprint = %fingerprint_hex,
73                        key_index = idx,
74                        "bytecode signature verified"
75                    );
76                    return VerificationResult {
77                        allowed: true,
78                        reason: "signature verified".into(),
79                        key_index: Some(idx),
80                    };
81                }
82                Err(SigningError::FingerprintMismatch) => {
83                    error!(
84                        fingerprint = %fingerprint_hex,
85                        "fingerprint mismatch — bytecode was tampered with"
86                    );
87                    return VerificationResult {
88                        allowed: false,
89                        reason: "fingerprint mismatch: bytecode was tampered with".into(),
90                        key_index: None,
91                    };
92                }
93                Err(SigningError::VerificationFailed) => {
94                    // This key didn't match; try the next one.
95                    continue;
96                }
97                Err(e) => {
98                    warn!(error = %e, "verification error");
99                    continue;
100                }
101            }
102        }
103
104        warn!(
105            fingerprint = %fingerprint_hex,
106            "no trusted key matched the signature"
107        );
108        VerificationResult {
109            allowed: false,
110            reason: "no trusted key matched the signature".into(),
111            key_index: None,
112        }
113    }
114
115    /// Verify bytecode or return an error message suitable for API responses.
116    pub fn verify_or_reject(&self, bytecode: &[u8], signature: &Signature) -> Result<usize, String> {
117        let result = self.verify(bytecode, signature);
118        if result.allowed {
119            Ok(result.key_index.unwrap())
120        } else {
121            Err(result.reason)
122        }
123    }
124
125    /// Check whether unsigned bytecode should be allowed (depends on strict mode).
126    pub fn allow_unsigned(&self) -> bool {
127        if self.strict {
128            warn!("unsigned bytecode rejected (strict mode)");
129            false
130        } else {
131            warn!("unsigned bytecode allowed (non-strict mode — not recommended for production)");
132            true
133        }
134    }
135}
136
137#[cfg(test)]
138mod tests {
139    use super::*;
140    use ed25519_dalek::SigningKey;
141
142    fn random_keypair() -> ([u8; 32], [u8; 32]) {
143        let signing_key = SigningKey::generate(&mut rand::rngs::OsRng);
144        let pk = signing_key.verifying_key().to_bytes();
145        let sk = signing_key.to_bytes();
146        (sk, pk)
147    }
148
149    #[test]
150    fn verify_with_trusted_key() {
151        let (sk, pk) = random_keypair();
152        let mw = VerificationMiddleware::new(vec![pk]);
153        let bytecode = b"LOAD x 42.0;";
154        let sig = signing::sign_bytecode(bytecode, &sk, Some(9999));
155        let result = mw.verify(bytecode, &sig);
156        assert!(result.allowed);
157    }
158
159    #[test]
160    fn reject_untrusted_key() {
161        let (sk, _) = random_keypair();
162        let (_, untrusted_pk) = random_keypair();
163        let mw = VerificationMiddleware::new(vec![untrusted_pk]);
164        let bytecode = b"LOAD x 42.0;";
165        let sig = signing::sign_bytecode(bytecode, &sk, Some(9999));
166        let result = mw.verify(bytecode, &sig);
167        assert!(!result.allowed);
168    }
169
170    #[test]
171    fn key_rotation_old_key_still_works() {
172        let (sk_old, pk_old) = random_keypair();
173        let (_sk_new, pk_new) = random_keypair();
174        let mut mw = VerificationMiddleware::new(vec![pk_old]);
175        mw.add_trusted_key(pk_new);
176        assert_eq!(mw.trusted_key_count(), 2);
177
178        // Old key signature should still verify.
179        let bytecode = b"LOAD x 42.0;";
180        let sig = signing::sign_bytecode(bytecode, &sk_old, Some(9999));
181        let result = mw.verify(bytecode, &sig);
182        assert!(result.allowed);
183    }
184
185    #[test]
186    fn strict_mode_rejects_unsigned() {
187        let (_, pk) = random_keypair();
188        let mw = VerificationMiddleware::new(vec![pk]).with_strict(true);
189        assert!(!mw.allow_unsigned());
190    }
191
192    #[test]
193    fn non_strict_mode_allows_unsigned() {
194        let (_, pk) = random_keypair();
195        let mw = VerificationMiddleware::new(vec![pk]).with_strict(false);
196        assert!(mw.allow_unsigned());
197    }
198}