Conversation
603ce0a to
e7d502b
Compare
1a7fbb4 to
a2523de
Compare
a2523de to
5160fd6
Compare
5160fd6 to
1a4ec15
Compare
0260601 to
67c4db2
Compare
jku
left a comment
There was a problem hiding this comment.
Some of this code I am not really able to review... but it seems correct to me and the concept certainly makes sense.
Having so many versions of verify/sign is a bit annoying but they all seem reasonable and serve a use case that's not covered by the other variants.
|
|
||
| pub fn finalize(self) -> ArtifactDigest { | ||
| ArtifactDigest::new(self.algorithm, self.context.finish().as_ref()) | ||
| .expect("cryptographic backend returned the algorithm's documented digest length") |
There was a problem hiding this comment.
nit: error message should say what went wrong, not the opposite
7ecbb04 to
0535811
Compare
Add constant-memory reader entry points for signing, certificate verification, and managed-key verification. Synchronous APIs accept std::io::Read; runtime-independent asynchronous APIs accept futures_io::AsyncRead. Reader input is consumed once while all required digests are computed concurrently. The verification core receives prepared artifact evidence so message signatures, DSSE subject binding, and Rekor consistency checks share those digests without retaining or rereading the full artifact. The main sign and verify entry points now accept enum-based artifact sources, with existing reader-specific functions kept as forwarding compatibility wrappers. BREAKING CHANGE: verification internals consume prepared artifact evidence and reader I/O errors are exposed explicitly. Signed-off-by: Wolf Vollprecht <w.vollprecht@gmail.com>
0535811 to
c20edeb
Compare
Signed-off-by: Wolf Vollprecht <w.vollprecht@gmail.com> # Conflicts: # crates/sigstore-crypto/src/hash.rs # crates/sigstore-verify/src/verify.rs # crates/sigstore-verify/src/verify_impl/hashedrekord.rs
Signed-off-by: Wolf Vollprecht <w.vollprecht@gmail.com> # Conflicts: # crates/sigstore-verify/src/lib.rs # crates/sigstore-verify/src/verify.rs # crates/sigstore-verify/tests/verification_tests.rs
Drop the ArtifactSource enums and the reader()/async_reader() wrappers. Each input shape gets its own method, and both verification paths expose the same three: Verifier::verify / verify_reader / verify_async_reader Verifier::verify_with_key / verify_with_key_reader / verify_with_key_async_reader Signer::sign / sign_reader / sign_async_reader The free verify() and verify_with_key() stay as thin wrappers over Verifier for bytes/digest input. Move the managed-key verification body onto Verifier so it uses the same trusted root as verify(). Add sync and async reader tests for managed-key bundles. BREAKING CHANGE: ArtifactSource, AsyncArtifactSource, reader(), async_reader(), verify_async(), sign_async(), and the free verify_reader/verify_async_reader/verify_with_key_reader/ verify_with_key_async_reader functions are removed. Signed-off-by: Wolf Vollprecht <w.vollprecht@gmail.com>
PreparedArtifact::digest() now returns an ArtifactDigest instead of raw bytes, and sha256()/sha512() helpers give the typed hashes the DSSE subject check and hashedrekord path need. This removes the compute_artifact_digest wrappers and the try_from_slice fallbacks that could not fail. Blob input hashes through ArtifactHasher, so the per-algorithm match disappears. Fold the duplicated messageDigest comparison in the certificate and managed-key paths into verify_message_digest_binding(). Signed-off-by: Wolf Vollprecht <w.vollprecht@gmail.com>
Blob input was rehashed on every digest() call, so a MessageSignature bundle could hash the same bytes up to three times. PreparedArtifact now hashes blobs once at construction, through the same single multi-hasher pass the reader path uses, and digest() is a plain lookup for every input kind. The set of algorithms is derived from the bundle content instead of a fixed SHA-256 + SHA-512: MessageSignature bundles need SHA-256 plus the declared messageDigest algorithm, DSSE bundles need SHA-256 and SHA-512 for in-toto subjects. A MessageSignature bundle therefore costs exactly one SHA-256 pass over the artifact. Signed-off-by: Wolf Vollprecht <w.vollprecht@gmail.com>
The in-toto statement is in the bundle, so read it before hashing the artifact and hash only with the algorithms its subjects use. Cosign and GitHub attestations bind SHA-256 only, so the common DSSE case is now a single SHA-256 pass like MessageSignature bundles. If the payload is not a readable statement, hash SHA-256 and SHA-512 so the binding check can report the real problem. Add Statement::subject_algorithms() to sigstore-types. Signed-off-by: Wolf Vollprecht <w.vollprecht@gmail.com>
The message read like a success condition. Say what went wrong instead, as requested in review. Signed-off-by: Wolf Vollprecht <w.vollprecht@gmail.com> Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…-io only The read-to-EOF-into-hashers loop existed four times (sync and async, in sigstore-sign and sigstore-verify) plus a chunked in-memory variant, each with its own copy of `yield_now`. Move one implementation into sigstore-crypto next to the hashers: - `hash_reader` feeds a blocking reader into any `HashUpdate` hashers - `hash_async_reader` does the same for `futures_io::AsyncRead`, yielding to the executor after every chunk (TOB-SIGSTORE-8) - `hash_reader_yielding` adapts a blocking reader, including in-memory slices, onto the async path so signing keeps its cooperative hashing The chunk-boundary and yield tests move with the code. Only `AsyncRead` itself is needed from the futures family now, so the library crates depend on `futures-io` instead of the full `futures` crate; `futures` stays a dev-dependency for `Cursor` in tests. Signed-off-by: Wolf Vollprecht <w.vollprecht@gmail.com> Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
`sign_sha256` validated the configuration only after the artifact had been hashed, so a misconfigured signer (Rekor v2 without a timestamp authority, for example) surfaced the error only after streaming a possibly large input. Check it first in every public entry point. Signed-off-by: Wolf Vollprecht <w.vollprecht@gmail.com> Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Streamed input can only be verified prehashed, so the single pass over the reader must produce the digest the signing scheme consumes. Until now the required algorithms were derived from the bundle content alone: a MessageSignature bundle without `messageDigest` whose key defaults to a non-SHA-256 scheme (P-384, RSA with SHA-512) verified from a blob but failed closed from a reader with "requires an sha2-384 artifact digest". Derive the scheme from the bundle's certificate, or the caller-supplied managed key, before reading and hash its digest alongside the binding digests. If the key material cannot be read up front, hash with every algorithm so verification proceeds far enough to report the real problem. Blob input is unchanged: the signature is verified over the bytes, so only the binding digests are computed. Signed-off-by: Wolf Vollprecht <w.vollprecht@gmail.com> Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The reader tests only exercised hashedrekord bundles, so the subject- algorithm selection for DSSE input had no end-to-end coverage. Verify the conda attestation (SHA-256 subject) from sync and async readers, reject a tampered streamed artifact, and bind a SHA-512-only subject from a reader. Signed-off-by: Wolf Vollprecht <w.vollprecht@gmail.com> Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…d digests Signed-off-by: Wolf Vollprecht <w.vollprecht@gmail.com>
|
Reader follow-up is complete and all checks on this PR are green. It now rejects invalid signing/verification inputs before consuming readers, retries Interrupted reads, yields during DSSE copying/hashing, and reuses prepared digests/statements. Additional hardening starts with #198; unrelated changes are kept out of this PR. |
baszalmstra
left a comment
There was a problem hiding this comment.
Took a quick look. Overall looks good to me.
| } | ||
| }; | ||
| verify(&artifact_bytes, &bundle, &policy, &trusted_root) | ||
| verifier.verify_reader(artifact, &bundle, &policy) |
There was a problem hiding this comment.
The example now unconditionally streams files. The reader API intentionally rejects Ed25519 message
signatures, whereas the previous byte API could verify them. This is code-traced, not reproduced with
an Ed25519 certificate fixture.
Could we preserve a byte-input path for schemes that cannot be prehashed? Switching every file to
verify_reader drops Ed25519 message-signature support. Those bundles could previously verify here,
and supplying a digest is not an alternative.
| fn block_on<F: Future>(future: F) -> F::Output { | ||
| // Every future here only ever yields once per chunk and re-wakes | ||
| // itself, so a spin loop with a no-op waker is a sufficient executor. | ||
| let waker = futures_task_noop_waker(); | ||
| let mut cx = TaskContext::from_waker(&waker); | ||
| let mut future = Box::pin(future); | ||
| loop { | ||
| if let Poll::Ready(output) = future.as_mut().poll(&mut cx) { | ||
| return output; | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
Why not use futures::executor::block_on?
| fn block_on<F: Future>(future: F) -> F::Output { | ||
| // Every future here only ever yields once per chunk and re-wakes | ||
| // itself, so a spin loop with a no-op waker is a sufficient executor. | ||
| let waker = futures_task_noop_waker(); |
There was a problem hiding this comment.
| struct CopyAndHash { | ||
| payload: Vec<u8>, | ||
| hasher: Sha256Hasher, | ||
| } | ||
| impl sigstore_crypto::HashUpdate for CopyAndHash { | ||
| fn update(&mut self, data: &[u8]) { | ||
| self.payload.extend_from_slice(data); | ||
| self.hasher.update(data); | ||
| } | ||
| } | ||
| let mut prepared = CopyAndHash { | ||
| payload: Vec::with_capacity(data.len()), | ||
| hasher: dsse_pae_hasher(payload_type, data.len()), | ||
| }; | ||
| hash_reader_yielding(data, std::slice::from_mut(&mut prepared)) | ||
| .await | ||
| .expect("reading from an in-memory slice cannot fail"); |
There was a problem hiding this comment.
Could we share the yielding update logic without routing in-memory slices through a reader buffer?
This now copies each chunk into the 64 KiB buffer before copying it into payload. The previous chunk
loop hashed and copied directly from the source slice.
| } | ||
|
|
||
| /// Adapter presenting a blocking [`Read`] as an always-ready [`AsyncRead`]. | ||
| struct BlockingReader<R>(R); |
There was a problem hiding this comment.
Maybe we can use https://docs.rs/futures/0.3.32/futures/io/struct.AllowStdIo.html ?
| /// | ||
| /// Reads block the calling thread. Inside an async task use | ||
| /// [`hash_reader_yielding`] or [`hash_async_reader`] instead. | ||
| pub fn hash_reader<H: HashUpdate>(mut reader: impl Read, hashers: &mut [H]) -> io::Result<()> { |
There was a problem hiding this comment.
Could we expose reader hashing through the existing hasher types rather than adding more free
functions? A shared trait could provide update_reader, update_reader_yielding, and update_async_reader, with a hasher collection supporting the same contract for single-pass
multi-digest hashing. That would also remove the SHA-256 reader wrappers in sign.rs, while keeping
the one-shot functions as conveniences.
Signed-off-by: Wolf Vollprecht <w.vollprecht@gmail.com>
Summary
Adds constant-memory artifact streaming to signing and verification.
Signing
Signer::sign(...)keeps accepting raw bytes or a typed SHA-256 digestSigner::sign_reader(...)streams a blockingstd::io::Readto EOFSigner::sign_async_reader(...)streams a runtime-independentfutures_io::AsyncReadThe artifact is hashed before the Fulcio certificate is requested, so large inputs no longer eat into the certificate's lifetime. The signer configuration is validated before any hashing starts.
Verification
Verifier::verify(...)and the freeverify(...)keep accepting raw bytes or a typed digestVerifier::verify_reader(...)andVerifier::verify_async_reader(...)stream the artifactverify_with_key,verify_with_key_readerandverify_with_key_async_readerverify_with_key(...)is now a thin wrapper over theVerifiermethod instead of a second copy of the verification flowReaders are consumed exactly once. A new crate-private
PreparedArtifacthashes the input in a single pass with every algorithm the bundle can ask for, and message-signature checks, DSSE subject binding, and Rekor consistency checks all draw on those digests without retaining or rereading the artifact. DSSE bundles hash only with the algorithms their in-toto subjects use; message-signature bundles hash SHA-256 for hashedrekord, the declaredmessageDigestalgorithm, and, for streamed input, the digest the signing scheme consumes (derived from the bundle's certificate or the supplied managed key up front).Blob input remains available for schemes such as Ed25519 that need the original message. Reader and digest input fail closed when the selected scheme does not support prehashed verification. Synchronous reader methods document that reads block the calling thread.
Shared hashing helpers
sigstore_cryptogainshash_reader,hash_reader_yieldingandhash_async_reader, which feed one pass over a reader into any set of hashers. The async variants yield to the executor after every 64 KiB chunk (TOB-SIGSTORE-8), and the signer's cooperative in-memory hashing now goes through the same code. The library crates depend onfutures-ioonly;futuresremains a dev-dependency.CLI examples stream files. Tests verify real hashedrekord and DSSE bundles through both sync and async readers, and unit tests pin the per-bundle digest selection.
Stack
Signed-off-by: Wolf Vollprecht w.vollprecht@gmail.com