Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Validate digests of data downloaded while fetching sigstore attachments #2689

Merged
merged 1 commit into from
Jan 21, 2025
Merged
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
13 changes: 13 additions & 0 deletions docker/docker_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -1056,6 +1056,15 @@ func (c *dockerClient) getBlob(ctx context.Context, ref dockerReference, info ty
func (c *dockerClient) getOCIDescriptorContents(ctx context.Context, ref dockerReference, desc imgspecv1.Descriptor, maxSize int, cache types.BlobInfoCache) ([]byte, error) {
// Note that this copies all kinds of attachments: attestations, and whatever else is there,
// not just signatures. We leave the signature consumers to decide based on the MIME type.

if err := desc.Digest.Validate(); err != nil { // .Algorithm() might panic without this check
return nil, fmt.Errorf("invalid digest %q: %w", desc.Digest.String(), err)
}
digestAlgorithm := desc.Digest.Algorithm()
if !digestAlgorithm.Available() {
return nil, fmt.Errorf("invalid digest %q: unsupported digest algorithm %q", desc.Digest.String(), digestAlgorithm.String())
}

reader, _, err := c.getBlob(ctx, ref, manifest.BlobInfoFromOCI1Descriptor(desc), cache)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks fine, but I think it'd be even clearer to merge getBlob and this function. Or at a minimum, rename getBlob to getRawBlobNoChecksumVerification or something...

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getBlob is primarily used by the public dockerImageSource.GetBlob, which must support streaming very large objects, and inherits both its interface and its (non-digesting) semantics.

And GetBlob is (sadly?) a public stable API. Having it automatically digest the contents would be interesting in the abstract, but all existing callers would should already be digesting it themselves, and doing that twice is rather costly, so I don’t think it makes sense to change the behavior of the existing function.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK sorry, I thought it was only called here.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

… another point, we don’t want every single transport to implement the digest validation itself; centralizing that in c/image/copy makes it easier to prove that the validation is always happening.

But at least the naming point is very apt; if we were ever redesigning the ImageSource API (it should be an object with methods, not an interface), GetBlob should be definitely renamed to something that emphasizes the caller needs to validate the contents — or maybe this should be centralized into the ImageSource-replacing wrapper object.

if err != nil {
return nil, err
Expand All @@ -1065,6 +1074,10 @@ func (c *dockerClient) getOCIDescriptorContents(ctx context.Context, ref dockerR
if err != nil {
return nil, fmt.Errorf("reading blob %s in %s: %w", desc.Digest.String(), ref.ref.Name(), err)
}
actualDigest := digestAlgorithm.FromBytes(payload)
if actualDigest != desc.Digest {
return nil, fmt.Errorf("digest mismatch, expected %q, got %q", desc.Digest.String(), actualDigest.String())
}
return payload, nil
}

Expand Down
Loading