|
| 1 | +# Hash Verification at Each Compilation Stage |
| 2 | + |
| 3 | +This document explains the hash verification callback system implemented across all compilers to enforce integrity checks at each stage of the compilation pipeline. |
| 4 | + |
| 5 | +## Overview |
| 6 | + |
| 7 | +The hash verification system provides **cryptographic proof** that files are not tampered with at any stage of compilation. Client code can subscribe to hash events to: |
| 8 | + |
| 9 | +- **Log all hash computations** for audit trails |
| 10 | +- **Enforce custom verification policies** (strict vs. permissive) |
| 11 | +- **Detect tampering** in real-time |
| 12 | +- **Track file integrity** across the pipeline |
| 13 | +- **Prevent MITM attacks** on downloaded sources |
| 14 | + |
| 15 | +## Architecture |
| 16 | + |
| 17 | +### Compilation Stages with Hash Verification |
| 18 | + |
| 19 | +``` |
| 20 | +┌─────────────────────────────────────────────────────────────┐ |
| 21 | +│ 1. Configuration File Loading │ |
| 22 | +│ ├─ Hash computed: config_file │ |
| 23 | +│ └─ Event fired: HashComputed │ |
| 24 | +└─────────────────────────────────────────────────────────────┘ |
| 25 | + │ |
| 26 | + ▼ |
| 27 | +┌─────────────────────────────────────────────────────────────┐ |
| 28 | +│ 2. Source Files Loading (Local & Remote) │ |
| 29 | +│ ├─ Hash computed: input_file / downloaded_source │ |
| 30 | +│ ├─ Event fired: HashComputed │ |
| 31 | +│ └─ Optional verification against expected hash │ |
| 32 | +│ ├─ Match → Event: HashVerified │ |
| 33 | +│ └─ Mismatch → Event: HashMismatch (can abort) │ |
| 34 | +└─────────────────────────────────────────────────────────────┘ |
| 35 | + │ |
| 36 | + ▼ |
| 37 | +┌─────────────────────────────────────────────────────────────┐ |
| 38 | +│ 3. Compilation (via hostlist-compiler) │ |
| 39 | +│ └─ (No hash events - external tool) │ |
| 40 | +└─────────────────────────────────────────────────────────────┘ |
| 41 | + │ |
| 42 | + ▼ |
| 43 | +┌─────────────────────────────────────────────────────────────┐ |
| 44 | +│ 4. Output File Writing │ |
| 45 | +│ ├─ Hash computed: output_file │ |
| 46 | +│ └─ Event fired: HashComputed │ |
| 47 | +└─────────────────────────────────────────────────────────────┘ |
| 48 | + │ |
| 49 | + ▼ |
| 50 | +┌─────────────────────────────────────────────────────────────┐ |
| 51 | +│ 5. Rules File Copying (if requested) │ |
| 52 | +│ ├─ Hash computed: copied_rules_file │ |
| 53 | +│ ├─ Event fired: HashComputed │ |
| 54 | +│ └─ Can verify against output hash │ |
| 55 | +│ ├─ Match → Event: HashVerified │ |
| 56 | +│ └─ Mismatch → Event: HashMismatch │ |
| 57 | +└─────────────────────────────────────────────────────────────┘ |
| 58 | +``` |
| 59 | + |
| 60 | +## Event Types |
| 61 | + |
| 62 | +### 1. Hash Computed Event |
| 63 | + |
| 64 | +Fired whenever a hash is computed for any file. |
| 65 | + |
| 66 | +**Data:** |
| 67 | +- `itemIdentifier`: File path or identifier |
| 68 | +- `itemType`: Type (e.g., "config_file", "output_file", "input_file") |
| 69 | +- `hash`: SHA-384 hash (96 hex characters) |
| 70 | +- `sizeBytes`: File size |
| 71 | +- `isVerification`: Whether this is for verification purposes |
| 72 | + |
| 73 | +### 2. Hash Verified Event |
| 74 | + |
| 75 | +Fired when a hash successfully matches the expected value. |
| 76 | + |
| 77 | +**Data:** |
| 78 | +- `itemIdentifier`: File path or identifier |
| 79 | +- `itemType`: Type of file |
| 80 | +- `expectedHash`: Expected SHA-384 hash |
| 81 | +- `actualHash`: Computed SHA-384 hash (should match expected) |
| 82 | +- `sizeBytes`: File size |
| 83 | +- `computationDurationMs`: Time taken to compute hash |
| 84 | + |
| 85 | +### 3. Hash Mismatch Event |
| 86 | + |
| 87 | +Fired when a hash does NOT match the expected value. |
| 88 | + |
| 89 | +**Data:** |
| 90 | +- `itemIdentifier`: File path or identifier |
| 91 | +- `itemType`: Type of file |
| 92 | +- `expectedHash`: Expected SHA-384 hash |
| 93 | +- `actualHash`: Computed SHA-384 hash (different from expected) |
| 94 | +- `sizeBytes`: File size |
| 95 | +- `abort`: Whether to abort compilation (default: true) |
| 96 | +- `abortReason`: Reason for aborting |
| 97 | +- `allowContinuation`: Handler can set this to continue despite mismatch |
| 98 | + |
| 99 | +**Handler Control:** |
| 100 | +- Set `allowContinuation = true` to continue despite mismatch |
| 101 | +- Set `abort = false` to prevent compilation failure |
| 102 | + |
| 103 | +## Implementation by Language |
| 104 | + |
| 105 | +### Rust |
| 106 | + |
| 107 | +**Event Handler Trait:** |
| 108 | +```rust |
| 109 | +use rules_compiler::events::{ |
| 110 | + CompilationEventHandler, |
| 111 | + HashComputedEventArgs, |
| 112 | + HashVerifiedEventArgs, |
| 113 | + HashMismatchEventArgs, |
| 114 | +}; |
| 115 | + |
| 116 | +struct MyHandler; |
| 117 | + |
| 118 | +impl CompilationEventHandler for MyHandler { |
| 119 | + fn on_hash_computed(&self, args: &HashComputedEventArgs) { |
| 120 | + println!("Hash for {}: {}", args.item_type, &args.hash[..16]); |
| 121 | + } |
| 122 | + |
| 123 | + fn on_hash_verified(&self, args: &HashVerifiedEventArgs) { |
| 124 | + println!("Hash verified for {}", args.item_identifier); |
| 125 | + } |
| 126 | + |
| 127 | + fn on_hash_mismatch(&self, args: &mut HashMismatchEventArgs) { |
| 128 | + eprintln!("Hash mismatch for {}", args.item_identifier); |
| 129 | + // Optionally allow continuation: |
| 130 | + // args.allow_continuation = true; |
| 131 | + // args.abort = false; |
| 132 | + } |
| 133 | +} |
| 134 | +``` |
| 135 | + |
| 136 | +**Usage:** |
| 137 | +```rust |
| 138 | +use rules_compiler::{compile_rules_with_events, EventDispatcher}; |
| 139 | + |
| 140 | +let mut dispatcher = EventDispatcher::new(); |
| 141 | +dispatcher.add_handler(Box::new(MyHandler)); |
| 142 | + |
| 143 | +let result = compile_rules_with_events("config.yaml", &options, &dispatcher)?; |
| 144 | +``` |
| 145 | + |
| 146 | +### TypeScript |
| 147 | + |
| 148 | +**Callback Interface:** |
| 149 | +```typescript |
| 150 | +import type { HashVerificationCallbacks } from './types.ts'; |
| 151 | + |
| 152 | +const callbacks: HashVerificationCallbacks = { |
| 153 | + onHashComputed: (event) => { |
| 154 | + console.log(`Hash for ${event.itemType}: ${event.hash.slice(0, 16)}...`); |
| 155 | + }, |
| 156 | + |
| 157 | + onHashVerified: (event) => { |
| 158 | + console.log(`Hash verified for ${event.itemIdentifier}`); |
| 159 | + }, |
| 160 | + |
| 161 | + onHashMismatch: (event) => { |
| 162 | + console.error(`Hash mismatch for ${event.itemIdentifier}`); |
| 163 | + // Optionally allow continuation: |
| 164 | + // event.allowContinuation = true; |
| 165 | + }, |
| 166 | +}; |
| 167 | +``` |
| 168 | + |
| 169 | +**Usage:** |
| 170 | +```typescript |
| 171 | +import { runCompiler } from './compiler.ts'; |
| 172 | + |
| 173 | +const result = await runCompiler({ |
| 174 | + configPath: 'config.yaml', |
| 175 | + hashCallbacks: callbacks, |
| 176 | +}); |
| 177 | +``` |
| 178 | + |
| 179 | +### .NET |
| 180 | + |
| 181 | +**Event Handler:** |
| 182 | +```csharp |
| 183 | +using RulesCompiler.Abstractions; |
| 184 | + |
| 185 | +public class MyHashHandler : CompilationEventHandlerBase |
| 186 | +{ |
| 187 | + public override Task OnHashComputedAsync( |
| 188 | + HashComputedEventArgs args, |
| 189 | + CancellationToken cancellationToken = default) |
| 190 | + { |
| 191 | + Console.WriteLine($"Hash for {args.ItemType}: {args.Hash.Substring(0, 16)}..."); |
| 192 | + return Task.CompletedTask; |
| 193 | + } |
| 194 | + |
| 195 | + public override Task OnHashVerifiedAsync( |
| 196 | + HashVerifiedEventArgs args, |
| 197 | + CancellationToken cancellationToken = default) |
| 198 | + { |
| 199 | + Console.WriteLine($"Hash verified for {args.ItemIdentifier}"); |
| 200 | + return Task.CompletedTask; |
| 201 | + } |
| 202 | + |
| 203 | + public override Task OnHashMismatchAsync( |
| 204 | + HashMismatchEventArgs args, |
| 205 | + CancellationToken cancellationToken = default) |
| 206 | + { |
| 207 | + Console.Error.WriteLine($"Hash mismatch for {args.ItemIdentifier}"); |
| 208 | + // Optionally allow continuation: |
| 209 | + // args.AllowContinuation = true; |
| 210 | + // args.Abort = false; |
| 211 | + return Task.CompletedTask; |
| 212 | + } |
| 213 | +} |
| 214 | +``` |
| 215 | + |
| 216 | +**Usage:** |
| 217 | +```csharp |
| 218 | +// To be implemented in compilation pipeline |
| 219 | +``` |
| 220 | + |
| 221 | +### Python |
| 222 | + |
| 223 | +To be implemented (similar pattern to Rust/TypeScript) |
| 224 | + |
| 225 | +## Use Cases |
| 226 | + |
| 227 | +### 1. Audit Trail Logging |
| 228 | + |
| 229 | +```rust |
| 230 | +impl CompilationEventHandler for AuditLogger { |
| 231 | + fn on_hash_computed(&self, args: &HashComputedEventArgs) { |
| 232 | + self.log(format!( |
| 233 | + "AUDIT: {} hash={} size={} timestamp={}", |
| 234 | + args.item_type, |
| 235 | + args.hash, |
| 236 | + args.size_bytes, |
| 237 | + chrono::Utc::now() |
| 238 | + )); |
| 239 | + } |
| 240 | +} |
| 241 | +``` |
| 242 | + |
| 243 | +### 2. Strict Zero-Trust Verification |
| 244 | + |
| 245 | +```rust |
| 246 | +impl CompilationEventHandler for StrictVerifier { |
| 247 | + fn on_hash_mismatch(&self, args: &mut HashMismatchEventArgs) { |
| 248 | + // Never allow continuation on mismatch |
| 249 | + args.abort = true; |
| 250 | + args.allow_continuation = false; |
| 251 | + self.alert_security_team(args); |
| 252 | + } |
| 253 | +} |
| 254 | +``` |
| 255 | + |
| 256 | +### 3. Permissive Development Mode |
| 257 | + |
| 258 | +```rust |
| 259 | +impl CompilationEventHandler for DevModeHandler { |
| 260 | + fn on_hash_mismatch(&self, args: &mut HashMismatchEventArgs) { |
| 261 | + // Log but don't fail in development |
| 262 | + eprintln!("WARN: Hash mismatch but allowing continuation in dev mode"); |
| 263 | + args.allow_continuation = true; |
| 264 | + args.abort = false; |
| 265 | + } |
| 266 | +} |
| 267 | +``` |
| 268 | + |
| 269 | +### 4. Database Tracking |
| 270 | + |
| 271 | +```rust |
| 272 | +impl CompilationEventHandler for DatabaseTracker { |
| 273 | + fn on_hash_computed(&self, args: &HashComputedEventArgs) { |
| 274 | + self.db.insert_hash_record( |
| 275 | + args.item_identifier.clone(), |
| 276 | + args.hash.clone(), |
| 277 | + chrono::Utc::now(), |
| 278 | + ); |
| 279 | + } |
| 280 | +} |
| 281 | +``` |
| 282 | + |
| 283 | +## Security Considerations |
| 284 | + |
| 285 | +1. **SHA-384 Algorithm**: All hashes use SHA-384 (96 hex characters) for cryptographic strength |
| 286 | +2. **At-Rest Verification**: Local files are hashed to detect tampering |
| 287 | +3. **In-Flight Verification**: Downloaded sources can be verified against expected hashes |
| 288 | +4. **MITM Prevention**: Hash verification on downloads prevents man-in-the-middle attacks |
| 289 | +5. **Immutable Audit Trail**: Hash events create an immutable log of all file states |
| 290 | + |
| 291 | +## Testing |
| 292 | + |
| 293 | +Example tests are included in `examples/hash_audit_handler.rs` demonstrating: |
| 294 | +- Strict verification (fails on mismatch) |
| 295 | +- Permissive verification (logs but continues) |
| 296 | +- Custom policy implementation |
| 297 | + |
| 298 | +## Example Handler |
| 299 | + |
| 300 | +See `examples/hash_audit_handler.rs` for a complete implementation of: |
| 301 | +- Logging all hash events |
| 302 | +- Customizable strictness (strict vs. permissive) |
| 303 | +- Comprehensive test coverage |
| 304 | + |
| 305 | +## Future Enhancements |
| 306 | + |
| 307 | +Potential additions: |
| 308 | +- Hash database persistence across compilations |
| 309 | +- Historical hash tracking and drift detection |
| 310 | +- Integration with external validation services |
| 311 | +- Support for multiple hash algorithms (SHA-256, BLAKE3) |
| 312 | +- Signature verification (GPG, minisign) |
0 commit comments