Skip to content
This repository was archived by the owner on Mar 24, 2026. It is now read-only.

Commit 9de0df4

Browse files
committed
Add security hardening, no_std support, fuzzing, and examples
1 parent bbc43a3 commit 9de0df4

17 files changed

Lines changed: 1071 additions & 66 deletions

Cargo.toml

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,23 +12,32 @@ categories = ["cryptography", "authentication"]
1212
readme = "README.md"
1313

1414
[dependencies]
15-
hmac = "0.12"
16-
sha2 = "0.10"
17-
serde = { version = "1.0", features = ["derive"] }
18-
serde_json = "1.0"
19-
thiserror = "2.0"
15+
hmac = { version = "0.12", default-features = false }
16+
sha2 = { version = "0.10", default-features = false }
17+
serde = { version = "1.0", default-features = false, features = ["derive", "alloc"] }
18+
serde_json = { version = "1.0", optional = true }
19+
thiserror = { version = "2.0", optional = true }
2020
getrandom = "0.3"
21-
zeroize = { version = "1.8", features = ["derive"] }
22-
subtle = "2.5"
21+
zeroize = { version = "1.8", default-features = false, features = ["derive", "alloc"] }
22+
subtle = { version = "2.5", default-features = false }
2323
rand = { version = "0.8", optional = true }
24-
hkdf = "0.12.4"
24+
hkdf = { version = "0.12.4", default-features = false }
25+
libm = "0.2"
2526

2627
[dev-dependencies]
2728
rand = "0.8"
29+
criterion = "0.5"
30+
serde_json = "1.0"
31+
32+
[[bench]]
33+
name = "benchmarks"
34+
harness = false
2835

2936
[features]
30-
default = []
31-
# Enable hardware entropy collection (TSC, etc.)
32-
hardware = []
33-
# Enable random secret generation
34-
rand = ["dep:rand"]
37+
default = ["std"]
38+
# Enable standard library (required for Session, timing, serde_json)
39+
std = ["dep:serde_json", "dep:thiserror", "sha2/std", "hmac/std", "serde/std", "zeroize/std", "hkdf/std"]
40+
# Enable hardware entropy collection (TSC, etc.) - requires std for timing fallback
41+
hardware = ["std"]
42+
# Enable random secret generation - requires std
43+
rand = ["dep:rand", "std"]

README.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -734,6 +734,33 @@ cargo test --all-features
734734
cargo test --no-default-features
735735
```
736736

737+
### Fuzzing
738+
739+
This crate includes fuzzing targets using `cargo-fuzz` to find edge cases and potential bugs:
740+
741+
| Target | Description |
742+
|--------|-------------|
743+
| `fuzz_evidence_json` | JSON deserialization of Evidence and EvidenceChain |
744+
| `fuzz_human_model` | HumanModel validation with arbitrary jitter/IKI values |
745+
| `fuzz_evidence_verify` | Evidence verification and chain integrity |
746+
| `fuzz_jitter_compute` | Jitter computation with various parameters |
747+
748+
```bash
749+
# Install cargo-fuzz (requires nightly)
750+
cargo install cargo-fuzz
751+
752+
# Run a specific fuzz target
753+
cargo +nightly fuzz run fuzz_evidence_json
754+
755+
# Run with a time limit (60 seconds)
756+
cargo +nightly fuzz run fuzz_human_model -- -max_total_time=60
757+
758+
# Run all fuzz targets briefly
759+
for target in fuzz_evidence_json fuzz_human_model fuzz_evidence_verify fuzz_jitter_compute; do
760+
cargo +nightly fuzz run $target -- -max_total_time=10
761+
done
762+
```
763+
737764
---
738765

739766
## FAQ

benches/benchmarks.rs

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
use criterion::{black_box, criterion_group, criterion_main, Criterion};
2+
use physjitter::{
3+
Evidence, EvidenceChain, HumanModel, HybridEngine, Jitter, JitterEngine, PureJitter,
4+
};
5+
6+
fn bench_pure_jitter(c: &mut Criterion) {
7+
let engine = PureJitter::default();
8+
let secret = [42u8; 32];
9+
let inputs = b"keystroke data";
10+
let entropy = [0u8; 32].into();
11+
12+
c.bench_function("PureJitter::compute_jitter", |b| {
13+
b.iter(|| engine.compute_jitter(black_box(&secret), black_box(inputs), black_box(entropy)))
14+
});
15+
}
16+
17+
fn bench_hybrid_engine(c: &mut Criterion) {
18+
let engine = HybridEngine::default();
19+
let secret = [42u8; 32];
20+
let inputs = b"keystroke data";
21+
22+
c.bench_function("HybridEngine::sample", |b| {
23+
b.iter(|| engine.sample(black_box(&secret), black_box(inputs)))
24+
});
25+
}
26+
27+
fn bench_evidence_chain_append(c: &mut Criterion) {
28+
let secret = [42u8; 32];
29+
30+
c.bench_function("EvidenceChain::append", |b| {
31+
b.iter_with_setup(
32+
|| EvidenceChain::with_secret(secret),
33+
|mut chain| {
34+
chain.append(Evidence::pure(1500));
35+
chain
36+
},
37+
)
38+
});
39+
}
40+
41+
fn bench_human_model_validate(c: &mut Criterion) {
42+
let model = HumanModel::default();
43+
let jitters: Vec<Jitter> = (0..1000).map(|i| 500 + ((i * 37) % 2500) as u32).collect();
44+
45+
c.bench_function("HumanModel::validate (1000 samples)", |b| {
46+
b.iter(|| model.validate(black_box(&jitters)))
47+
});
48+
}
49+
50+
fn bench_evidence_chain_verify_integrity(c: &mut Criterion) {
51+
let secret = [42u8; 32];
52+
let mut chain = EvidenceChain::with_secret(secret);
53+
for i in 0..100 {
54+
chain.append(Evidence::pure(500 + (i % 2500) as u32));
55+
}
56+
57+
c.bench_function("EvidenceChain::verify_integrity (100 records)", |b| {
58+
b.iter(|| chain.verify_integrity(black_box(&secret)))
59+
});
60+
}
61+
62+
criterion_group!(
63+
benches,
64+
bench_pure_jitter,
65+
bench_hybrid_engine,
66+
bench_evidence_chain_append,
67+
bench_human_model_validate,
68+
bench_evidence_chain_verify_integrity,
69+
);
70+
criterion_main!(benches);

examples/basic_session.rs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
//! Basic session usage example.
2+
//!
3+
//! Run with: `cargo run --example basic_session`
4+
5+
use physjitter::Session;
6+
7+
fn main() -> Result<(), Box<dyn std::error::Error>> {
8+
// Create a session with a secret key
9+
// In production, derive this from a secure source!
10+
let secret = [42u8; 32];
11+
let mut session = Session::new(secret);
12+
13+
// Simulate typing keystrokes
14+
let keystrokes = ["H", "e", "l", "l", "o", " ", "W", "o", "r", "l", "d", "!"];
15+
16+
println!("Simulating {} keystrokes...", keystrokes.len());
17+
18+
for keystroke in keystrokes {
19+
let jitter_us = session.sample(keystroke.as_bytes())?;
20+
println!(" '{}' -> {}μs jitter", keystroke, jitter_us);
21+
22+
// In a real application, you would apply this delay
23+
// std::thread::sleep(std::time::Duration::from_micros(jitter_us as u64));
24+
}
25+
26+
// Need more samples for validation
27+
for i in 0..20 {
28+
session.sample(format!("extra{}", i).as_bytes())?;
29+
}
30+
31+
// Validate against human typing model
32+
let result = session.validate();
33+
println!("\nValidation Results:");
34+
println!(" Is human: {}", result.is_human);
35+
println!(" Confidence: {:.2}", result.confidence);
36+
println!(" Anomalies: {}", result.anomalies.len());
37+
38+
// Export evidence
39+
let _json = session.export_json()?;
40+
println!(
41+
"\nEvidence chain has {} records",
42+
session.evidence().records.len()
43+
);
44+
println!("Physics ratio: {:.1}%", session.phys_ratio() * 100.0);
45+
46+
Ok(())
47+
}

examples/custom_engine.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
//! Custom engine configuration example.
2+
//!
3+
//! Run with: `cargo run --example custom_engine --features hardware`
4+
5+
use physjitter::{HybridEngine, PhysJitter, PureJitter};
6+
7+
fn main() {
8+
// Create custom engines with specific parameters
9+
let phys = PhysJitter::new(8) // Require 8 bits minimum entropy
10+
.with_jitter_range(1000, 2000); // 1000-3000μs range
11+
12+
let pure = PureJitter::new(1000, 2000); // Matching range for fallback
13+
14+
let engine = HybridEngine::new(phys, pure).with_min_entropy(8);
15+
16+
println!("Custom engine created");
17+
println!(" Physics available: {}", engine.phys_available());
18+
19+
// Sample some jitter
20+
let secret = [42u8; 32];
21+
for i in 0..5 {
22+
let (jitter, evidence) = engine
23+
.sample(&secret, format!("input{}", i).as_bytes())
24+
.unwrap();
25+
println!(
26+
" Sample {}: {}μs ({})",
27+
i,
28+
jitter,
29+
if evidence.is_phys() {
30+
"physics"
31+
} else {
32+
"pure"
33+
}
34+
);
35+
}
36+
}

examples/verify_evidence.rs

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
//! Evidence chain verification example.
2+
//!
3+
//! Run with: `cargo run --example verify_evidence`
4+
5+
use physjitter::{Evidence, EvidenceChain, JitterEngine, PureJitter};
6+
7+
fn main() {
8+
let secret = [42u8; 32];
9+
let engine = PureJitter::default();
10+
11+
// Build an evidence chain
12+
let inputs: Vec<&[u8]> = vec![b"key1", b"key2", b"key3", b"key4", b"key5"];
13+
let mut chain = EvidenceChain::with_secret(secret);
14+
15+
println!("Building evidence chain...");
16+
for input in &inputs {
17+
let jitter = engine.compute_jitter(&secret, input, [0u8; 32].into());
18+
chain.append(Evidence::pure(jitter));
19+
println!(
20+
" Added evidence for {:?} -> {}μs",
21+
String::from_utf8_lossy(input),
22+
jitter
23+
);
24+
}
25+
26+
// Verify chain integrity
27+
println!("\nVerifying chain integrity...");
28+
let integrity_ok = chain.verify_integrity(&secret);
29+
println!(
30+
" Integrity check: {}",
31+
if integrity_ok { "PASSED" } else { "FAILED" }
32+
);
33+
34+
// Verify against original inputs
35+
let chain_ok = chain.verify_chain(&secret, &inputs, &engine);
36+
println!(
37+
" Chain verification: {}",
38+
if chain_ok { "PASSED" } else { "FAILED" }
39+
);
40+
41+
// Try with wrong inputs
42+
let wrong_inputs: Vec<&[u8]> = vec![b"wrong1", b"wrong2", b"wrong3", b"wrong4", b"wrong5"];
43+
let wrong_ok = chain.verify_chain(&secret, &wrong_inputs, &engine);
44+
println!(
45+
" Wrong inputs check: {}",
46+
if !wrong_ok {
47+
"CORRECTLY REJECTED"
48+
} else {
49+
"ERROR"
50+
}
51+
);
52+
53+
// Tamper detection demo
54+
println!("\nTamper detection demo...");
55+
let mut tampered_chain = chain.clone();
56+
if let Some(Evidence::Pure { jitter, .. }) = tampered_chain.records.get_mut(2) {
57+
*jitter = 9999;
58+
}
59+
let tamper_detected = !tampered_chain.verify_integrity(&secret);
60+
println!(
61+
" Tamper detected: {}",
62+
if tamper_detected { "YES" } else { "NO" }
63+
);
64+
}

fuzz/.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
target/
2+
corpus/
3+
artifacts/
4+
coverage/

fuzz/Cargo.toml

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
[package]
2+
name = "physjitter-fuzz"
3+
version = "0.0.0"
4+
publish = false
5+
edition = "2021"
6+
7+
[package.metadata]
8+
cargo-fuzz = true
9+
10+
[dependencies]
11+
libfuzzer-sys = "0.4"
12+
arbitrary = { version = "1", features = ["derive"] }
13+
serde_json = "1.0"
14+
15+
[dependencies.physjitter]
16+
path = ".."
17+
18+
[[bin]]
19+
name = "fuzz_evidence_json"
20+
path = "fuzz_targets/fuzz_evidence_json.rs"
21+
test = false
22+
doc = false
23+
bench = false
24+
25+
[[bin]]
26+
name = "fuzz_human_model"
27+
path = "fuzz_targets/fuzz_human_model.rs"
28+
test = false
29+
doc = false
30+
bench = false
31+
32+
[[bin]]
33+
name = "fuzz_evidence_verify"
34+
path = "fuzz_targets/fuzz_evidence_verify.rs"
35+
test = false
36+
doc = false
37+
bench = false
38+
39+
[[bin]]
40+
name = "fuzz_jitter_compute"
41+
path = "fuzz_targets/fuzz_jitter_compute.rs"
42+
test = false
43+
doc = false
44+
bench = false
45+
46+
[workspace]
47+
members = ["."]
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
#![no_main]
2+
3+
use libfuzzer_sys::fuzz_target;
4+
use physjitter::{Evidence, EvidenceChain};
5+
6+
fuzz_target!(|data: &[u8]| {
7+
// Fuzz Evidence deserialization
8+
if let Ok(s) = std::str::from_utf8(data) {
9+
// Try to parse as Evidence
10+
let _ = serde_json::from_str::<Evidence>(s);
11+
12+
// Try to parse as EvidenceChain
13+
let _ = serde_json::from_str::<EvidenceChain>(s);
14+
}
15+
16+
// Also test with raw bytes interpreted as potentially valid JSON
17+
let _ = serde_json::from_slice::<Evidence>(data);
18+
let _ = serde_json::from_slice::<EvidenceChain>(data);
19+
});

0 commit comments

Comments
 (0)