From f7794e92b7bb1312cb8233beb3664b75eb0ff103 Mon Sep 17 00:00:00 2001 From: Ralf Anton Beier Date: Tue, 11 Aug 2026 22:06:01 +0200 Subject: [PATCH] =?UTF-8?q?release:=20v0.11.0=20=E2=80=94=20offline=20veri?= =?UTF-8?q?fication,=20real=20and=20lightweight?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bump 0.10.0 -> 0.11.0 across Cargo.toml, MODULE.bazel, src/cli/BUILD.bazel VERSION, all internal wsc-* dep pins (incl the new wsc-dsse), and Cargo.lock. Ships REQ-23 (real offline keyless verify, #219), REQ-24 (no_std wsc-dsse, #218), REQ-25 (verify-core MC/DC gap closed + baseline 5->3, #128) — all verified, clean-room checked, now accepted. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_012aR3Md1h46K9wAUWMQiESH --- CHANGELOG.md | 56 +++++++++++++++++++++++++++++++++++++ Cargo.lock | 12 ++++---- Cargo.toml | 2 +- MODULE.bazel | 2 +- artifacts/dev/features.yaml | 6 ++-- src/cli/BUILD.bazel | 2 +- src/cli/Cargo.toml | 2 +- src/component/Cargo.toml | 2 +- src/crypto/Cargo.toml | 2 +- src/lib/Cargo.toml | 6 ++-- 10 files changed, 74 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f8de0c..a9d850f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,62 @@ All notable changes to sigil are documented here. The project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.11.0] — 2026-08-11 + +Offline verification, made real and lightweight. The airgapped keyless verifier +stops being a stub, DSSE gets a `no_std` home for embedded/on-target consumers, +and verify-core's own MC/DC decision reaches full coverage. + +### Added + +- **Real offline keyless verification (#219 / REQ-23).** `AirGappedVerifier`'s + crypto path was a stub — ed25519 over the full SPKI DER (fails for any real + Fulcio cert, which are ECDSA P-256), no cert-chain anchoring, no Rekor SET + check. Replaced with genuine offline Sigstore verification that **delegates to + the same tested primitives the online path uses**, anchored to the provisioned + `TrustBundle`: cert chain (`from_pem_authorities` → `verify_pem_cert` at the + Rekor `integrated_time`, RFC-5280 + codeSigning EKU) → Rekor **SET** + (`from_pem_logs` → `verify_set`) → **P-256** signature over `module_hash` via + `verify_prehash` → **Rekor body-binding** (#135 UCA-2) → revocation. The broken + `extract_public_key_from_cert` is deleted. *Falsification:* 11 offline tests + each reject at their own step (wrong root→chain, expired→time, wrong Rekor + key→SET, tampered→P-256, body-mismatch→body-binding, revoked→revocation, hash + mismatch→step 1); the positive path builds a real hashedrekord body so + body-binding is genuinely exercised. **Not verified offline** (stated, not + hidden): Rekor Merkle inclusion (mirrors the online skip, Rekor v2 shard bug / + #137 — `VerificationResult.inclusion_verified = false`) and SCT (no CT-log key + is provisioned in the bundle). `SigstoreBundle::verify` split to #231. + +- **`wsc-dsse` — a `no_std` DSSE crate (#218 / REQ-24).** `wsc::dsse` (envelope + sign/verify) lived only in the 179-crate `wsc`; extracted into a standalone + `no_std` + `alloc` crate whose only deps are base64/serde/serde_json/ + ed25519-compact, so embedded/offline consumers (varve, #187's Cortex-M + on-target verifier) get DSSE without the registry/TLS/X.509 tree. Builds for + `thumbv7em-none-eabi`. `wsc` re-exports it (`pub use wsc_dsse as dsse`) with + `From for WSError`, so the public API and internal callers compile + unchanged. *Falsification:* the thumbv7em build is the embedded proof; wsc-dsse + 97% line coverage; `wsc` suite unchanged. + +### Changed + +- **Witness MC/DC gate: verify-core's coverable decision reaches full MC/DC; + scoped baseline 5 → 3 (#128 / REQ-25).** Of the two `Partial` decisions the + scoped gate counted, only one is verify-core's own logic — the WASM-header + compare in `Module::init_from_reader`; a scenario for the `WASM_COMPONENT_HEADER` + accept path drives it to full MC/DC (0→1 fully-covered decisions). The residual + 3 gaps are the **inlined** `<&[u8] as Read>::read_exact` decision's copy + branches (vary only with buffer length; verify-core reads at lengths 1 and 8 + only) — infeasible without gaming; documented, with the `^src/` scoping + imprecision filed as #241. Baseline tightened 5→3; gate potency re-verified + (baseline 2 → red; deleting the header reads → gap 4>3 → red). + +### Verification notes + +Feature-loop coverage: REQ-23 landed via delegated implementation whose **missing +Rekor body-binding was caught on review** (a #135 UCA-2 wrong-accept vector) and +added before merge; all three items were clean-room verified. Steps 1–2 (spar +AADL → WIT) N/A — no architecture/interface change. + ## [0.10.0] — 2026-08-07 Provenance identity and claim honesty. Records *which qualified toolchain-set* diff --git a/Cargo.lock b/Cargo.lock index f2a020d..a2c4673 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4650,7 +4650,7 @@ checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" [[package]] name = "wsc" -version = "0.10.0" +version = "0.11.0" dependencies = [ "anyhow", "base64 0.22.1", @@ -4702,7 +4702,7 @@ dependencies = [ [[package]] name = "wsc-attestation" -version = "0.10.0" +version = "0.11.0" dependencies = [ "base64 0.22.1", "chrono", @@ -4718,7 +4718,7 @@ dependencies = [ [[package]] name = "wsc-cli" -version = "0.10.0" +version = "0.11.0" dependencies = [ "clap", "env_logger", @@ -4731,7 +4731,7 @@ dependencies = [ [[package]] name = "wsc-component" -version = "0.10.0" +version = "0.11.0" dependencies = [ "wit-bindgen 0.51.0", "wsc", @@ -4739,7 +4739,7 @@ dependencies = [ [[package]] name = "wsc-dsse" -version = "0.10.0" +version = "0.11.0" dependencies = [ "base64 0.22.1", "ed25519-compact", @@ -4749,7 +4749,7 @@ dependencies = [ [[package]] name = "wsc-verify-core" -version = "0.10.0" +version = "0.11.0" dependencies = [ "ct-codecs", "ed25519-compact", diff --git a/Cargo.toml b/Cargo.toml index 89fc9a4..5efc8e7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,7 @@ exclude = [ ] [workspace.package] -version = "0.10.0" +version = "0.11.0" edition = "2024" authors = ["Frank Denis ", "Ralf Anton Beier "] license = "MIT" diff --git a/MODULE.bazel b/MODULE.bazel index 3e765ca..8c7c891 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -3,7 +3,7 @@ module( name = "wsc", # Keep in sync with [workspace.package].version in Cargo.toml. - version = "0.10.0", + version = "0.11.0", ) # Dependencies diff --git a/artifacts/dev/features.yaml b/artifacts/dev/features.yaml index cadf49a..5bdce7b 100644 --- a/artifacts/dev/features.yaml +++ b/artifacts/dev/features.yaml @@ -653,7 +653,7 @@ artifacts: - id: REQ-23 type: requirement title: Real offline keyless verification (Rekor SET + Fulcio chain + ECDSA P-256) - status: verified + status: accepted description: "AirGappedVerifier::verify_crypto is a stub: it neither verifies the Rekor SET nor anchors the cert chain to the bundle's Fulcio roots, and extract_public_key_from_cert feeds full SPKI DER to an ed25519-only path that requires 32 raw bytes — so it fails for any real Fulcio cert (which are ECDSA P-256). Implement genuine offline Sigstore keyless verification: verify the Rekor inclusion/SET, anchor the leaf cert to provisioned Fulcio roots, support ECDSA P-256 (curve-agile, per DD-8), and add an in-library SigstoreBundle::verify(&trust_bundle, artifact_digest) so there is a verify-blob --bundle equivalent that does not shell out to cosign. Fails-closed today (errors), so not a silent-accept hole, but the advertised offline-keyless path is unusable. Unblocks varve offline manifest verification." tags: [soundness, keyless, offline, airgapped] fields: @@ -667,7 +667,7 @@ artifacts: - id: REQ-24 type: requirement title: Lightweight DSSE verify — extract dsse off the 179-crate wsc tree - status: verified + status: accepted description: "wsc::dsse (DSSE sign/verify over arbitrary blobs — what a downstream offline verifier wants) lives only in the full wsc crate, whose default build pulls 179 crates (ring, rustls, ureq, rcgen, x509-parser, p256, webpki-roots). The dsse module's real deps are just base64, serde, serde_json, ed25519-compact. Move dsse into wsc-verify-core (21 crates) or a new wsc-dsse crate so an embedded/offline consumer (varve, the on-target verifier in #187) can verify DSSE without the registry/network stack. Complements the offline-keyless work." tags: [offline, dsse, embedded, dependency-hygiene] fields: @@ -681,7 +681,7 @@ artifacts: - id: REQ-25 type: requirement title: Close verify-core's own MC/DC Partial gaps (witness scenarios) - status: draft + status: accepted description: "The witness MC/DC gate is now scoped to verify-core's own decisions (#128, v0.10.0). Its two decisions — varint.rs:29 and wasm_module/mod.rs:455 — are Partial: 5 gap conditions on linux CI (3 + 2). Add witness scenarios that supply the missing unique-cause rows so both reach full MC/DC, then lower SRC_BASELINE_GAP accordingly. Turns the gate from regression-guard into a coverage-complete gate for verify-core." tags: [mcdc, witness, verification-completeness] fields: diff --git a/src/cli/BUILD.bazel b/src/cli/BUILD.bazel index 43d4dc4..6f815b5 100644 --- a/src/cli/BUILD.bazel +++ b/src/cli/BUILD.bazel @@ -15,7 +15,7 @@ package(default_visibility = ["//visibility:public"]) # Version info - keep in sync with [workspace.package].version in Cargo.toml # and with the `version` field in MODULE.bazel. -VERSION = "0.10.0" +VERSION = "0.11.0" rust_binary( name = "wasmsign_cli", diff --git a/src/cli/Cargo.toml b/src/cli/Cargo.toml index afac1a3..6cf9650 100644 --- a/src/cli/Cargo.toml +++ b/src/cli/Cargo.toml @@ -32,5 +32,5 @@ regex = "1.12.2" # HTTP client for keyless signing ureq = { version = "3.1.2" } wasi = { version = "0.14.7" } -wsc = { version = "0.10.0", path = "../lib" } +wsc = { version = "0.11.0", path = "../lib" } serde_json = "1.0" diff --git a/src/component/Cargo.toml b/src/component/Cargo.toml index 1d3001d..4d1c088 100644 --- a/src/component/Cargo.toml +++ b/src/component/Cargo.toml @@ -12,7 +12,7 @@ crate-type = ["cdylib"] [dependencies] # Core signing library -wsc = { version = "0.10.0", path = "../lib" } +wsc = { version = "0.11.0", path = "../lib" } # WIT bindings generation wit-bindgen = { version = "0.51.0", default-features = false, features = ["realloc"] } diff --git a/src/crypto/Cargo.toml b/src/crypto/Cargo.toml index 10563be..757a655 100644 --- a/src/crypto/Cargo.toml +++ b/src/crypto/Cargo.toml @@ -12,7 +12,7 @@ crate-type = ["cdylib"] [dependencies] # Core signing library (provides platform module) -wsc = { version = "0.10.0", path = "../lib", features = ["software-keys"] } +wsc = { version = "0.11.0", path = "../lib", features = ["software-keys"] } # WIT bindings generation wit-bindgen = { version = "0.47.0", default-features = false, features = ["realloc"] } diff --git a/src/lib/Cargo.toml b/src/lib/Cargo.toml index a6669e8..d836226 100644 --- a/src/lib/Cargo.toml +++ b/src/lib/Cargo.toml @@ -14,12 +14,12 @@ categories = ["cryptography", "wasm"] [dependencies] # Verification core (carved out so it builds for wasm32 with no TLS/X.509 deps). # wsc re-exports its public API for backwards compatibility. -wsc-verify-core = { version = "0.10.0", path = "../verify-core" } +wsc-verify-core = { version = "0.11.0", path = "../verify-core" } # Re-export attestation types from minimal crate -wsc-attestation = { version = "0.10.0", path = "../attestation" } +wsc-attestation = { version = "0.11.0", path = "../attestation" } # DSSE sign/verify carved out as a no_std crate (issue #218 / REQ-24). # wsc re-exports it as `wsc::dsse` for backwards compatibility. -wsc-dsse = { version = "0.10.0", path = "../dsse" } +wsc-dsse = { version = "0.11.0", path = "../dsse" } anyhow = "1.0.100" ct-codecs = "1.1.6" ed25519-compact = { version = "2.1.1", features = ["pem"] }