Skip to main content

coreason_runtime_rust/execution_plane/
integrity.rs

1// Copyright (c) 2026 CoReason, Inc.
2// All rights reserved.
3
4//! Structural Integrity — Zero-trust module self-checksumming.
5//!
6//! Replaces `coreason_runtime/execution_plane/integrity.py`.
7//!
8//! Enforces the zero-trust structural integrity of the runtime by providing
9//! self-checksumming capabilities. Mathematically hashes source content at
10//! runtime to detect "Fork-and-Patch" attacks where critical cryptographic
11//! components (like the license verifier) might have been tampered with.
12//!
13//! Zero Waste: SHA-256 delegated to `sha2` (MIT/Apache-2.0).
14//! Ed25519 signature verification delegated to `ed25519-dalek` (BSD-3-Clause).
15//! Memory-mapped file I/O delegated to `memmap2` (MIT/Apache-2.0).
16//! Thread-safe caching delegated to `dashmap` (MIT).
17
18use dashmap::DashMap;
19use ed25519_dalek::{Signature, Verifier, VerifyingKey};
20use sha2::{Digest, Sha256};
21use std::convert::TryInto;
22use std::sync::OnceLock;
23
24/// Thread-safe module integrity verification cache.
25///
26/// Once a module is verified, its hash is cached to avoid redundant
27/// disk I/O on hot paths.
28fn verified_cache() -> &'static DashMap<String, String> {
29    static CACHE: OnceLock<DashMap<String, String>> = OnceLock::new();
30    CACHE.get_or_init(DashMap::new)
31}
32
33/// Raised when structural integrity verification fails.
34#[derive(Debug, Clone)]
35pub struct IntegrityViolationError {
36    pub message: String,
37}
38
39impl std::fmt::Display for IntegrityViolationError {
40    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
41        write!(f, "IntegrityViolation: {}", self.message)
42    }
43}
44
45impl std::error::Error for IntegrityViolationError {}
46
47/// Check if a module has already been verified (cache hit).
48pub fn check_cache(module_name: &str, expected_hash: &str) -> bool {
49    verified_cache()
50        .get(module_name)
51        .map_or(false, |v| v.value() == expected_hash)
52}
53
54/// Store a successful verification result in the cache.
55pub fn update_cache(module_name: &str, expected_hash: &str) {
56    verified_cache().insert(module_name.to_string(), expected_hash.to_string());
57}
58
59/// Clear the entire verification cache.
60pub fn clear_cache() {
61    verified_cache().clear();
62}
63
64/// Verify source code integrity via SHA-256 checksum.
65///
66/// Normalizes CRLF → LF before hashing for cross-platform consistency.
67/// Zero Waste: SHA-256 delegated to `sha2`.
68pub fn verify_module_integrity(source_code: &str, expected_sha256: &str) -> bool {
69    let normalized = source_code.replace("\r\n", "\n");
70    let mut hasher = Sha256::new();
71    hasher.update(normalized.as_bytes());
72    let actual_hash = format!("{:x}", hasher.finalize());
73    actual_hash == expected_sha256
74}
75
76/// Verify a file's SHA-256 hash using memory-mapped I/O.
77///
78/// Zero Waste: Uses `memmap2` for zero-copy file reading and `sha2` for hashing.
79pub fn verify_file_hash(file_path: &str, expected_sha256: &str) -> Result<bool, String> {
80    use memmap2::Mmap;
81    use std::fs::File;
82
83    let file = File::open(file_path).map_err(|e| format!("Failed to open file: {}", e))?;
84    let mmap = unsafe { Mmap::map(&file).map_err(|e| format!("Failed to mmap file: {}", e))? };
85
86    // Normalize CRLF → LF on the fly
87    let mut normalized = Vec::with_capacity(mmap.len());
88    let mut i = 0;
89    while i < mmap.len() {
90        if i + 1 < mmap.len() && mmap[i] == b'\r' && mmap[i + 1] == b'\n' {
91            normalized.push(b'\n');
92            i += 2;
93        } else {
94            normalized.push(mmap[i]);
95            i += 1;
96        }
97    }
98
99    let mut hasher = Sha256::new();
100    hasher.update(&normalized);
101    let actual_hash = format!("{:x}", hasher.finalize());
102    Ok(actual_hash == expected_sha256)
103}
104
105/// Verify an Ed25519 signature over a message.
106///
107/// Zero Waste: Ed25519 delegated to `ed25519-dalek` (BSD-3-Clause).
108pub fn verify_signature(
109    message: &str,
110    signature_hex: &str,
111    public_key_hex: &str,
112) -> Result<bool, String> {
113    let public_bytes = hex::decode(public_key_hex).map_err(|e| e.to_string())?;
114    let signature_bytes = hex::decode(signature_hex).map_err(|e| e.to_string())?;
115
116    let public_array: [u8; 32] = public_bytes
117        .as_slice()
118        .try_into()
119        .map_err(|_| "Public key must be 32 bytes".to_string())?;
120    let signature_array: [u8; 64] = signature_bytes
121        .as_slice()
122        .try_into()
123        .map_err(|_| "Signature must be 64 bytes".to_string())?;
124
125    let verifying_key = VerifyingKey::from_bytes(&public_array)
126        .map_err(|e| format!("Invalid public key: {}", e))?;
127    let signature = Signature::from_bytes(&signature_array);
128
129    Ok(verifying_key.verify(message.as_bytes(), &signature).is_ok())
130}
131
132/// Full module integrity check with caching.
133///
134/// First checks the cache, then verifies the source code hash.
135/// Updates the cache on successful verification.
136pub fn assert_module_integrity(
137    module_name: &str,
138    source_code: &str,
139    expected_hash: &str,
140) -> Result<(), IntegrityViolationError> {
141    // Check cache first
142    if check_cache(module_name, expected_hash) {
143        return Ok(());
144    }
145
146    // Verify hash
147    if !verify_module_integrity(source_code, expected_hash) {
148        let normalized = source_code.replace("\r\n", "\n");
149        let mut hasher = Sha256::new();
150        hasher.update(normalized.as_bytes());
151        let actual_hash = format!("{:x}", hasher.finalize());
152
153        return Err(IntegrityViolationError {
154            message: format!(
155                "CRITICAL INTEGRITY VIOLATION: Module {} has been tampered with! \
156                 Expected hash {}... but got {}...",
157                module_name,
158                &expected_hash[..expected_hash.len().min(8)],
159                &actual_hash[..actual_hash.len().min(8)],
160            ),
161        });
162    }
163
164    // Cache successful verification
165    update_cache(module_name, expected_hash);
166    Ok(())
167}
168
169#[cfg(test)]
170mod tests {
171    use super::*;
172
173    #[test]
174    fn test_verify_module_integrity() {
175        let source = "fn main() { println!(\"hello\"); }";
176        let mut hasher = Sha256::new();
177        hasher.update(source.as_bytes());
178        let expected = format!("{:x}", hasher.finalize());
179
180        assert!(verify_module_integrity(source, &expected));
181        assert!(!verify_module_integrity(source, "badhash"));
182    }
183
184    #[test]
185    fn test_verify_module_normalizes_crlf() {
186        let source_lf = "line1\nline2";
187        let source_crlf = "line1\r\nline2";
188
189        let mut hasher = Sha256::new();
190        hasher.update(source_lf.as_bytes());
191        let expected = format!("{:x}", hasher.finalize());
192
193        assert!(verify_module_integrity(source_lf, &expected));
194        assert!(verify_module_integrity(source_crlf, &expected));
195    }
196
197    #[test]
198    fn test_cache_operations() {
199        let test_key = "test_mod_ops_unique";
200        // Do not clear cache to avoid interfering with other parallel tests
201        update_cache(test_key, "hash123");
202        assert!(check_cache(test_key, "hash123"));
203        assert!(!check_cache(test_key, "wrong_hash"));
204    }
205
206    #[test]
207    fn test_assert_module_integrity_valid() {
208        let test_key = "test_mod_valid_unique";
209        let source = "test source code";
210        let mut hasher = Sha256::new();
211        hasher.update(source.as_bytes());
212        let expected = format!("{:x}", hasher.finalize());
213
214        assert!(assert_module_integrity(test_key, source, &expected).is_ok());
215        // Should be cached now
216        assert!(check_cache(test_key, &expected));
217    }
218
219    #[test]
220    fn test_assert_module_integrity_tampered() {
221        let test_key = "test_mod_tampered_unique";
222        let source = "original source";
223        let result = assert_module_integrity(test_key, source, "badhash0000");
224        assert!(result.is_err());
225        assert!(result
226            .unwrap_err()
227            .message
228            .contains("CRITICAL INTEGRITY VIOLATION"));
229    }
230
231    #[test]
232    fn test_assert_module_integrity_cached() {
233        // Use a unique key with timestamp to avoid any parallel test interference
234        let unique_key = format!(
235            "cached_mod_{}_{}",
236            std::process::id(),
237            std::time::SystemTime::now()
238                .duration_since(std::time::UNIX_EPOCH)
239                .unwrap()
240                .as_nanos()
241        );
242        // Insert directly and immediately verify
243        update_cache(&unique_key, "known_hash");
244        assert!(check_cache(&unique_key, "known_hash"));
245        // The assert_module_integrity should hit the cache and skip hash verification
246        assert!(assert_module_integrity(&unique_key, "doesn't matter", "known_hash").is_ok());
247    }
248
249    #[test]
250    fn test_hex_decode() {
251        assert_eq!(hex::decode("48656c6c6f").unwrap(), b"Hello");
252        assert!(hex::decode("abc").is_err()); // Odd length
253        assert!(hex::decode("zz").is_err()); // Invalid hex
254    }
255}