Reported from varve, which depends on wsc for DSSE and is evaluating keyless signing to remove long-lived realm root keys entirely.
The gap
Verification is already general. airgapped::AirgappedVerifier::verify_signature takes a 32-byte hash:
pub fn verify_signature(
&self,
signature: &KeylessSignature,
module_hash: &[u8; 32],
) -> Result<VerificationResult, WSError>
Signing is not. KeylessSigner::sign_module(&self, module: Module) requires a WASM Module.
So a caller with bytes that are not a WASM module — varve signs a layer manifest, whose sha256 is exactly a 32-byte digest — can verify with wsc but cannot sign with it.
Ask
A signing entry point over a digest, mirroring what the verifier already accepts:
pub fn sign_digest(&self, digest: &[u8; 32]) -> Result<KeylessSignature, WSError>
sign_module then becomes sign_digest(hash_of(module)) plus the module-embedding step, so there is one keyless signing path rather than two.
Why this matters beyond varve
The airgapped/ module is, as far as I can tell, the only implementation of offline Sigstore keyless verification with a provisioned Trust Bundle in the Rust ecosystem — with real TUF support (tuf.rs, 471 lines, no stubs), a bundle grace period, and an explicit unreliable-time fallback for devices with no clock.
That is valuable to anything signing artifacts for air-gapped consumers, not just WASM. The Module requirement on the signing side is the only thing making it WASM-specific, and it is not load-bearing — the verifier already proves the design is hash-based underneath.
Context
varve is considering replacing its realm root (a long-lived ed25519 key that it can neither rotate nor revoke — both stated as permanent limits in its own threat model) with keyless identity signing. The blocker for that was always offline verification for air-gapped consumers, and wsc::airgapped solves it. This entry point is what stands between the two halves.
Recorded on the varve side as DD-025. Happy to contribute the patch if you would rather review than write it.
Reported from varve, which depends on
wscfor DSSE and is evaluating keyless signing to remove long-lived realm root keys entirely.The gap
Verification is already general.
airgapped::AirgappedVerifier::verify_signaturetakes a 32-byte hash:Signing is not.
KeylessSigner::sign_module(&self, module: Module)requires a WASMModule.So a caller with bytes that are not a WASM module — varve signs a layer manifest, whose sha256 is exactly a 32-byte digest — can verify with wsc but cannot sign with it.
Ask
A signing entry point over a digest, mirroring what the verifier already accepts:
sign_modulethen becomessign_digest(hash_of(module))plus the module-embedding step, so there is one keyless signing path rather than two.Why this matters beyond varve
The
airgapped/module is, as far as I can tell, the only implementation of offline Sigstore keyless verification with a provisioned Trust Bundle in the Rust ecosystem — with real TUF support (tuf.rs, 471 lines, no stubs), a bundle grace period, and an explicit unreliable-time fallback for devices with no clock.That is valuable to anything signing artifacts for air-gapped consumers, not just WASM. The
Modulerequirement on the signing side is the only thing making it WASM-specific, and it is not load-bearing — the verifier already proves the design is hash-based underneath.Context
varve is considering replacing its realm root (a long-lived ed25519 key that it can neither rotate nor revoke — both stated as permanent limits in its own threat model) with keyless identity signing. The blocker for that was always offline verification for air-gapped consumers, and
wsc::airgappedsolves it. This entry point is what stands between the two halves.Recorded on the varve side as DD-025. Happy to contribute the patch if you would rather review than write it.