Skip to content

Commit 5158f68

Browse files
authored
[WIP] Add hash checks enforcement during compilation stages (#206)
1 parent 3f30b6b commit 5158f68

13 files changed

Lines changed: 1305 additions & 5 deletions

File tree

docs/HASH_VERIFICATION.md

Lines changed: 312 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,312 @@
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)

src/rules-compiler-dotnet/src/RulesCompiler/Abstractions/CompilationEventHandlerBase.cs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,4 +89,22 @@ public virtual Task OnCompilationErrorAsync(
8989
CompilationErrorEventArgs args,
9090
CancellationToken cancellationToken = default)
9191
=> Task.CompletedTask;
92+
93+
/// <inheritdoc/>
94+
public virtual Task OnHashComputedAsync(
95+
HashComputedEventArgs args,
96+
CancellationToken cancellationToken = default)
97+
=> Task.CompletedTask;
98+
99+
/// <inheritdoc/>
100+
public virtual Task OnHashVerifiedAsync(
101+
HashVerifiedEventArgs args,
102+
CancellationToken cancellationToken = default)
103+
=> Task.CompletedTask;
104+
105+
/// <inheritdoc/>
106+
public virtual Task OnHashMismatchAsync(
107+
HashMismatchEventArgs args,
108+
CancellationToken cancellationToken = default)
109+
=> Task.CompletedTask;
92110
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
namespace RulesCompiler.Abstractions;
2+
3+
/// <summary>
4+
/// Event arguments for when a hash is computed for an item.
5+
/// </summary>
6+
public class HashComputedEventArgs : CompilationEventArgs
7+
{
8+
/// <summary>
9+
/// Gets the path or identifier for the item being hashed.
10+
/// </summary>
11+
public string ItemIdentifier { get; }
12+
13+
/// <summary>
14+
/// Gets the type of item (e.g., "input_file", "output_file", "downloaded_source").
15+
/// </summary>
16+
public string ItemType { get; }
17+
18+
/// <summary>
19+
/// Gets the computed SHA-384 hash (96 hex characters).
20+
/// </summary>
21+
public string Hash { get; }
22+
23+
/// <summary>
24+
/// Gets the size of the item in bytes.
25+
/// </summary>
26+
public long SizeBytes { get; }
27+
28+
/// <summary>
29+
/// Gets whether this is for verification purposes.
30+
/// </summary>
31+
public bool IsVerification { get; }
32+
33+
/// <summary>
34+
/// Initializes a new instance of the <see cref="HashComputedEventArgs"/> class.
35+
/// </summary>
36+
/// <param name="options">The compiler options.</param>
37+
/// <param name="itemIdentifier">The path or identifier for the item.</param>
38+
/// <param name="itemType">The type of item.</param>
39+
/// <param name="hash">The computed hash.</param>
40+
/// <param name="sizeBytes">The size in bytes.</param>
41+
/// <param name="isVerification">Whether this is for verification purposes.</param>
42+
public HashComputedEventArgs(
43+
CompilerOptions options,
44+
string itemIdentifier,
45+
string itemType,
46+
string hash,
47+
long sizeBytes,
48+
bool isVerification = false)
49+
: base(options)
50+
{
51+
ItemIdentifier = itemIdentifier ?? throw new ArgumentNullException(nameof(itemIdentifier));
52+
ItemType = itemType ?? throw new ArgumentNullException(nameof(itemType));
53+
Hash = hash ?? throw new ArgumentNullException(nameof(hash));
54+
SizeBytes = sizeBytes;
55+
IsVerification = isVerification;
56+
}
57+
}

0 commit comments

Comments
 (0)