Skip to content

Commit ae441cf

Browse files
rayketchamclaude
andcommitted
docs: speed-run demo + showcase — measured 11ms total, reproducible receipt
- Rewrote speed-run.cast as a teaching run: why sign, what-we-sign per format (PE/CAB/MSI/PS1/detached CMS), crypto stack (RSA/ECDSA/Ed25519/ ML-DSA experimental + PKCS#7/CMS + RFC 3161), measured splits, verify, competitive set. Numbers are no longer hand-picked. - New #[ignore]d bench `bench_speedrun_all_formats` in signer.rs builds minimal PE/CAB/MSI/PS1/tarball with the bundled RSA-2048 fixture, times each sign path, and prints SPEEDRUN_BENCH receipt anyone can reproduce. Measured: PE 3ms · CAB 2ms · MSI 2ms · PS1 2ms · CMS 2ms → 11ms total. - Re-render speed-run.svg from new cast, play-once (no loop). - README hero points back at speed-run.svg with the "five formats, one binary, proof all the way down" framing. - Speed Run tab restored in docs/demo.html at position 2 with matching DEMOS[] entry; other tabs renumbered. - New `pki-sign demo` subcommand (src/demo.rs) + `demo` cargo feature (default-on) — self-contained sign+verify round-trip against bundled throwaway PFX. Exclude for hardened builds: --no-default-features. - Showcase gallery (docs/showcase.html + thumbs) ships alongside the per-cast SVGs for the-intern, the-heist, quantum-panic, the-auditor, the-api so the landing grid has rendered assets. Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 4815943 commit ae441cf

21 files changed

Lines changed: 747 additions & 70 deletions

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ No OpenSSL. No `signtool.exe`. No external dependencies. One binary.
88

99
<p align="center">
1010
<a href="https://rayketcham-lab.github.io/PKI-Signing-Service/demo.html">
11-
<img src="docs/demos/speed-run.svg" alt="pki-sign demo — sign and verify a batch of Windows binaries in under 10s" width="800">
11+
<img src="docs/demos/speed-run.svg" alt="pki-sign demo — sign five Windows formats (PE, CAB, MSI, PS1, detached CMS) on Linux from one static binary, with the full chain of proof" width="800">
1212
</a>
1313
<br>
1414
<em>▶ <a href="https://rayketcham-lab.github.io/PKI-Signing-Service/demo.html">Watch the full demo suite (6 scripted casts)</a></em>

crates/pki-sign/Cargo.toml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,12 @@ csv = { workspace = true }
9898
http-body-util = "0.1"
9999

100100
[features]
101-
default = []
101+
default = ["demo"]
102+
# Self-contained `pki-sign demo` subcommand. Embeds a throwaway RSA-2048
103+
# PFX (from tests/fixtures) so new users can watch pki-sign sign + verify
104+
# a Windows PE end-to-end in under a second without needing their own
105+
# cert. Default-on. Exclude for hardened builds with --no-default-features.
106+
demo = []
102107
# Experimental post-quantum signing (ML-DSA / SLH-DSA).
103108
#
104109
# Default-off because the ml-dsa 0.0.4 crate is pre-1.0 and carries open

crates/pki-sign/src/demo.rs

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
//! `pki-sign demo` — self-contained, in-process signing demo.
2+
//!
3+
//! Runs against a bundled throwaway RSA-2048 PFX so new users can watch
4+
//! pki-sign sign and verify a Windows PE end-to-end in under a second,
5+
//! without needing their own cert.
6+
//!
7+
//! Gated behind the `demo` feature (default-on). Build hardened releases
8+
//! that exclude the bundled test PFX with:
9+
//! cargo build --release --no-default-features
10+
11+
use anyhow::{Context, Result};
12+
use colored::Colorize;
13+
use std::time::Instant;
14+
15+
const DEMO_PFX: &[u8] = include_bytes!("../tests/fixtures/rsa2048.pfx");
16+
const DEMO_PFX_PASSWORD: &str = "test";
17+
18+
/// Run the scripted signing demo. Writes its own temp working dir and
19+
/// cleans it up on return.
20+
pub async fn run() -> Result<()> {
21+
println!();
22+
println!(
23+
"{}",
24+
" pki-sign demo — end-to-end sign + verify in-process"
25+
.bold()
26+
.cyan()
27+
);
28+
println!(
29+
"{}",
30+
" (bundled throwaway RSA-2048 cert — not a real signing key)".dimmed()
31+
);
32+
println!();
33+
34+
let tempdir = tempfile::tempdir().context("create tempdir")?;
35+
36+
let t0 = Instant::now();
37+
let pfx_path = tempdir.path().join("demo.pfx");
38+
std::fs::write(&pfx_path, DEMO_PFX).context("write bundled pfx")?;
39+
let credentials = crate::signer::SigningCredentials::from_pfx(&pfx_path, DEMO_PFX_PASSWORD)
40+
.context("load bundled demo credentials")?;
41+
println!(
42+
" {} credentials loaded {}",
43+
"✔".green().bold(),
44+
format!("({:.0?})", t0.elapsed()).dimmed()
45+
);
46+
47+
let t1 = Instant::now();
48+
let pe_bytes = make_minimal_pe();
49+
let pe_path = tempdir.path().join("demo.exe");
50+
std::fs::write(&pe_path, &pe_bytes).context("write demo PE")?;
51+
println!(
52+
" {} generated minimal PE32 ({} bytes) {}",
53+
"✔".green().bold(),
54+
pe_bytes.len(),
55+
format!("({:.0?})", t1.elapsed()).dimmed()
56+
);
57+
58+
let t2 = Instant::now();
59+
let signed_path = tempdir.path().join("demo-signed.exe");
60+
let sign_result = crate::signer::sign_file(&pe_path, &signed_path, &credentials, None)
61+
.await
62+
.context("sign demo PE")?;
63+
println!(
64+
" {} signed — original SHA-256 {}…, signed SHA-256 {}… {}",
65+
"✔".green().bold(),
66+
&sign_result.original_hash[..12],
67+
&sign_result.signed_hash[..12],
68+
format!("({:.0?})", t2.elapsed()).dimmed()
69+
);
70+
71+
let t3 = Instant::now();
72+
let verify_result = crate::verifier::verify_file(&signed_path).context("verify signed PE")?;
73+
if !verify_result.signature_valid {
74+
anyhow::bail!("demo signature failed self-verification — this should never happen");
75+
}
76+
println!(
77+
" {} verified — digest {} {}",
78+
"✔".green().bold(),
79+
verify_result.digest_algorithm,
80+
format!("({:.0?})", t3.elapsed()).dimmed()
81+
);
82+
println!(
83+
" {} {}",
84+
"signer:".dimmed(),
85+
verify_result.signer_subject
86+
);
87+
88+
let total = t0.elapsed();
89+
println!();
90+
println!(
91+
" {} {}",
92+
"Total round-trip:".bold(),
93+
format!("{:.0?}", total).green().bold()
94+
);
95+
println!();
96+
println!(" {}", "Next steps:".bold());
97+
println!(" 1. Use your own code-signing PFX:");
98+
println!(
99+
" {}",
100+
"export PKI_SIGN_PFX_PASSWORD='your-password'".dimmed()
101+
);
102+
println!(
103+
" {}",
104+
"pki-sign sign --pfx your-cert.pfx your-app.exe".dimmed()
105+
);
106+
println!(
107+
" 2. Verify: {}",
108+
"pki-sign verify your-app-signed.exe".dimmed()
109+
);
110+
println!();
111+
Ok(())
112+
}
113+
114+
/// Build a minimal valid PE32 file suitable for demo signing.
115+
///
116+
/// Mirrors the private `make_minimal_pe32()` helper in
117+
/// `crate::pe::parser` (which is `#[cfg(test)]` and therefore not
118+
/// reachable from a release build).
119+
fn make_minimal_pe() -> Vec<u8> {
120+
let mut data = vec![0u8; 512];
121+
data[0] = b'M';
122+
data[1] = b'Z';
123+
data[0x3C] = 0x80;
124+
data[0x80] = b'P';
125+
data[0x81] = b'E';
126+
data[0x86] = 1;
127+
data[0x94] = 0xE0;
128+
data[0x98] = 0x0B;
129+
data[0x99] = 0x01;
130+
data[0xF4] = 16;
131+
data[0x189] = 0x02;
132+
data[0x18D] = 0x02;
133+
data
134+
}

crates/pki-sign/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@
3838
3939
pub mod cab;
4040
pub mod config;
41+
#[cfg(feature = "demo")]
42+
pub mod demo;
4143
pub mod error;
4244
pub mod msi;
4345
pub mod pe;

crates/pki-sign/src/main.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,10 @@ enum Commands {
141141
prefix: PathBuf,
142142
},
143143

144+
/// Run an end-to-end sign + verify demo against a bundled throwaway cert
145+
#[cfg(feature = "demo")]
146+
Demo,
147+
144148
/// Time-Stamp Authority (RFC 3161) server commands
145149
Tsa {
146150
#[command(subcommand)]
@@ -472,6 +476,17 @@ fn main() {
472476
eprintln!("Setup wizard not yet implemented");
473477
std::process::exit(1);
474478
}
479+
#[cfg(feature = "demo")]
480+
Commands::Demo => {
481+
let rt = tokio::runtime::Builder::new_current_thread()
482+
.enable_all()
483+
.build()
484+
.unwrap();
485+
if let Err(e) = rt.block_on(pki_sign::demo::run()) {
486+
eprintln!("demo failed: {e:#}");
487+
std::process::exit(1);
488+
}
489+
}
475490
Commands::Tsa { command } => match command {
476491
TsaCommands::Serve {
477492
bind,

crates/pki-sign/src/signer.rs

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4215,4 +4215,85 @@ mod tests {
42154215
}
42164216

42174217
// ─── Debug: dump raw CAB signature for osslsigncode diagnosis ───
4218+
4219+
/// Reproducible timing receipt for the `speed-run.cast` demo.
4220+
///
4221+
/// Signs a minimal PE, CAB, MSI, PS1, and detached CMS target with the
4222+
/// bundled RSA-2048 PFX, printing the measured wall-clock time for each.
4223+
/// Run with:
4224+
/// cargo test --release -p pki-sign -- --nocapture bench_speedrun_all_formats
4225+
#[tokio::test]
4226+
#[ignore = "benchmark — opt-in via --ignored"]
4227+
async fn bench_speedrun_all_formats() {
4228+
use std::time::Instant;
4229+
4230+
let pfx_path = fixture_pfx("rsa2048.pfx");
4231+
let credentials =
4232+
SigningCredentials::from_pfx(&pfx_path, "test").expect("load RSA-2048 fixture");
4233+
4234+
let tempdir = tempfile::tempdir().expect("tempdir");
4235+
let dir = tempdir.path();
4236+
4237+
let pe_in = dir.join("bench.exe");
4238+
std::fs::write(&pe_in, build_test_pe()).expect("write PE");
4239+
let cab_in = dir.join("bench.cab");
4240+
std::fs::write(&cab_in, build_interop_test_cab(64)).expect("write CAB");
4241+
let msi_in = dir.join("bench.msi");
4242+
std::fs::write(&msi_in, build_interop_test_msi()).expect("write MSI");
4243+
let ps1_in = dir.join("bench.ps1");
4244+
std::fs::write(&ps1_in, b"Write-Host 'pki-sign speedrun'\n").expect("write PS1");
4245+
let tar_in = dir.join("bench.tar.gz");
4246+
std::fs::write(&tar_in, vec![0xAAu8; 4096]).expect("write tarball");
4247+
4248+
let mut times_ms = Vec::<(&str, u128)>::new();
4249+
4250+
let pe_out = dir.join("bench-signed.exe");
4251+
let t = Instant::now();
4252+
sign_file(&pe_in, &pe_out, &credentials, None)
4253+
.await
4254+
.expect("sign PE");
4255+
times_ms.push(("PE", t.elapsed().as_millis()));
4256+
4257+
let cab_out = dir.join("bench-signed.cab");
4258+
let t = Instant::now();
4259+
sign_file(&cab_in, &cab_out, &credentials, None)
4260+
.await
4261+
.expect("sign CAB");
4262+
times_ms.push(("CAB", t.elapsed().as_millis()));
4263+
4264+
let msi_out = dir.join("bench-signed.msi");
4265+
let t = Instant::now();
4266+
sign_file(&msi_in, &msi_out, &credentials, None)
4267+
.await
4268+
.expect("sign MSI");
4269+
times_ms.push(("MSI", t.elapsed().as_millis()));
4270+
4271+
let ps1_out = dir.join("bench-signed.ps1");
4272+
let t = Instant::now();
4273+
sign_file(&ps1_in, &ps1_out, &credentials, None)
4274+
.await
4275+
.expect("sign PS1");
4276+
times_ms.push(("PS1", t.elapsed().as_millis()));
4277+
4278+
let t = Instant::now();
4279+
let det = sign_detached(&tar_in, &credentials, None)
4280+
.await
4281+
.expect("sign detached CMS");
4282+
times_ms.push(("CMS", t.elapsed().as_millis()));
4283+
std::fs::write(dir.join("bench.tar.gz.p7s"), &det.p7s_data).expect("write .p7s");
4284+
4285+
let total: u128 = times_ms.iter().map(|(_, ms)| ms).sum();
4286+
eprintln!("\nSPEEDRUN_BENCH pki-sign={}", env!("CARGO_PKG_VERSION"));
4287+
for (label, ms) in &times_ms {
4288+
eprintln!(" {:<4} {} ms", label, ms);
4289+
}
4290+
eprintln!(" TOTAL {} ms ({:.3} s)", total, total as f64 / 1000.0);
4291+
4292+
let verify = crate::verifier::verify_file(&pe_out).expect("verify PE");
4293+
assert!(verify.signature_valid, "PE self-verify failed");
4294+
eprintln!(
4295+
"\nVERIFY (PE) digest={} signer={} valid={}",
4296+
verify.digest_algorithm, verify.signer_subject, verify.signature_valid
4297+
);
4298+
}
42184299
}

docs/demo.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1038,7 +1038,7 @@ <h2 class="section-title" id="demos-heading">Pick Your Adventure</h2>
10381038
aria-controls="player-panel"
10391039
aria-selected="false"
10401040
data-index="2"
1041-
data-tooltip="PE, CAB, MSI, PS1, detached. Any% no major glitches."
1041+
data-tooltip="Five formats. One binary. Proof all the way down."
10421042
>
10431043
<span class="tab-emoji" aria-hidden="true">&#9889;</span>
10441044
Speed Run
@@ -1235,7 +1235,7 @@ <h2 class="section-title" id="install-heading">Up and Running in 60 Seconds</h2>
12351235
var DEMOS = [
12361236
{ id: 'intern', cast: 'demos/the-intern.cast', title: "The Intern", desc: "Kevin's first day. What could go wrong?" },
12371237
{ id: 'heist', cast: 'demos/the-heist.cast', title: "The Heist", desc: "Somebody tampered with the binary. Big mistake." },
1238-
{ id: 'speed-run', cast: 'demos/speed-run.cast', title: "Speed Run", desc: "5 file types. 5 seconds. World record attempt." },
1238+
{ id: 'speedrun', cast: 'demos/speed-run.cast', title: "Speed Run", desc: "Five formats. One binary. Authenticode + PKCS#7 + RFC 3161 — proof all the way down." },
12391239
{ id: 'quantum', cast: 'demos/quantum-panic.cast', title: "Quantum Panic", desc: "RSA is dead. Long live ML-DSA (experimental: pq-experimental feature flag)." },
12401240
{ id: 'auditor', cast: 'demos/the-auditor.cast', title: "The Auditor", desc: "Karen from Compliance has entered the chat." },
12411241
{ id: 'api', cast: 'demos/the-api.cast', title: "REST in Peace", desc: "Where unsigned code goes to die." }

docs/demos/quantum-panic.svg

Lines changed: 1 addition & 0 deletions
Loading

0 commit comments

Comments
 (0)