Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<DsseError> 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*
Expand Down
12 changes: 6 additions & 6 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ exclude = [
]

[workspace.package]
version = "0.10.0"
version = "0.11.0"
edition = "2024"
authors = ["Frank Denis <github@pureftpd.org>", "Ralf Anton Beier <ralf_beier@me.com>"]
license = "MIT"
Expand Down
2 changes: 1 addition & 1 deletion MODULE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 3 additions & 3 deletions artifacts/dev/features.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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:
Expand All @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion src/cli/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion src/cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
2 changes: 1 addition & 1 deletion src/component/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"] }
2 changes: 1 addition & 1 deletion src/crypto/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"] }
Expand Down
6 changes: 3 additions & 3 deletions src/lib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"] }
Expand Down
Loading