Conversation
d0538c1 to
12421ba
Compare
12421ba to
671608f
Compare
jku
left a comment
There was a problem hiding this comment.
I don't quite undertand why the sign_prehashed() argument is Sha256Hasher and not the digest
- we call finish_digest() anyway as the first thing in sign_prehashed
- when we call
sha256_yielding()we always await anyway
It's possible I don't fully grasp the async rust model... I guess what I'm asking is :
Does this design have an advantage over just adding sigstore_crypto::sha256_yielding() that has the same signature as sigstore_crypto::sha256 but yields
671608f to
19434e8
Compare
… executor (TOB-SIGSTORE-8) Trail of Bits finding TOB-SIGSTORE-8 (informational, denial of service): Signer::sign synchronously hashed and ECDSA-signed unbounded Artifact::Bytes input inside an async fn, so a large artifact could occupy an executor worker thread for its entire duration and starve other tasks. KeyPair::sign additionally hashed the full message a second time inside aws-lc-rs before producing the signature. Mechanism: - sigstore-crypto grows KeyPair::sign_prehashed(Sha256Hasher), built on aws-lc-rs EcdsaKeyPair::sign_digest. It signs the incrementally computed SHA-256 digest directly, producing a signature that verifies equivalently to ECDSA-SHA256 over the raw message while making the signing step O(1) in the message size (and dropping the SystemRandom handle the full-message path constructs). - sigstore-sign hashes caller-controlled input (artifact bytes and DSSE PAE bytes) in 64 KiB chunks without materializing a full PAE copy, yielding to the executor between chunks via a small runtime-agnostic yield_now future (no new dependency, works on any executor), then signs the precomputed digest. - Signer::sign and Signer::sign_raw_statement document the behavior; statement JSON validation stays synchronous since statements carry metadata, not artifact contents. Tests cover chunked-vs-one-shot digest equivalence and that prehashed signatures verify as ECDSA-SHA256 over the full message. Signed-off-by: Wolf Vollprecht <w.vollprecht@gmail.com>
19434e8 to
9898492
Compare
|
@jku There is one important advantage to carrying |
|
Superseded by the upstream stack #183 → #184 → #185. #183 preserves the incremental/prehashed foundation from this PR; #184 adds typed blob/digest inputs and makes digest signing work; #185 adds constant-memory sync and async reader APIs for signing and verification. Closing this fork-based version so review can continue on the real stack. |
Summary
Fixes Trail of Bits finding TOB-SIGSTORE-8 (severity: informational, category: denial of service): "Raw artifact signing performs potentially blocking actions in async context."
Signer::signsynchronously hashed and ECDSA-signed unboundedArtifact::Bytesinput inside an async fn. On a large artifact this occupies an async executor worker thread for the whole duration, starving other tasks.KeyPair::signalso re-hashed the entire message inside aws-lc-rs before signing (and constructed aSystemRandom, which may perform I/O for entropy).Mechanism
Two complementary changes, chosen over
spawn_blockingbecauseSigner::signborrows the artifact bytes (Artifact<'a>), andspawn_blockingrequires'static— copying a potentially huge artifact to satisfy that would defeat the purpose:sigstore-crypto): newKeyPair::sign_prehashed(Sha256Hasher) -> (Sha256Hash, SignatureBytes), built onaws_lc_rs::EcdsaKeyPair::sign_digest(available and algorithm-checked in the pinned aws-lc-rs 1.17). Signing the precomputed SHA-256 digest produces a signature that verifies equivalently to ECDSA-SHA256 over the raw message — the hashedrekord signature remains verifiable exactly as before — while making the signing step O(1) in the message size. It also drops theSystemRandomhandle used by the full-message path.sigstore-sign): caller-controlled input (artifact bytes insign, DSSE PAE bytes insign_raw_statementandcreate_dsse_rekor_entry) is now hashed without materializing a second full-size PAE buffer in 64 KiB chunks with a yield to the executor between chunks. The yield is a ~20-line runtime-agnosticyield_nowfuture (wake +Pendingonce), so no tokio runtime dependency is added to the library and the code works on any executor.The public API is unchanged.
Signer::signandSigner::sign_raw_statementdocument the executor behavior. Statement JSON validation insign_raw_statementstays synchronous: statements carry metadata (subject digests, predicates), not artifact contents, so they are realistically small.Testing
sha256_yieldingchunked hashing matches one-shotsigstore_crypto::sha256across boundary sizes (0, 1, chunk±1, multi-chunk).VerificationKey::verify) as ECDSA-SHA256 over the full raw message, in bothsigstore-cryptoand through thesigstore-signhelper.cargo fmt,cargo clippy -p sigstore-sign -p sigstore-crypto --all-targets -- -D warnings,cargo test -p sigstore-sign -p sigstore-crypto(all pass, incl. doc-tests),cargo check --workspace.🤖 Generated with Claude Code