Skip to content

Commit 0984e11

Browse files
authored
Merge pull request #486 from lsm5/digest-redux-image
image/internal: validate blob against digest
2 parents a68a419 + a762705 commit 0984e11

File tree

4 files changed

+131
-7
lines changed

4 files changed

+131
-7
lines changed
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package image
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/opencontainers/go-digest"
7+
)
8+
9+
func validateBlobAgainstDigest(blob []byte, expectedDigest digest.Digest) error {
10+
if expectedDigest == "" {
11+
return fmt.Errorf("expected digest is empty")
12+
}
13+
err := expectedDigest.Validate()
14+
if err != nil {
15+
return fmt.Errorf("invalid digest format %q: %w", expectedDigest, err)
16+
}
17+
digestAlgorithm := expectedDigest.Algorithm()
18+
if !digestAlgorithm.Available() {
19+
return fmt.Errorf("unsupported digest algorithm: %s", digestAlgorithm)
20+
}
21+
computedDigest := digestAlgorithm.FromBytes(blob)
22+
if computedDigest != expectedDigest {
23+
return fmt.Errorf("blob digest %s does not match expected %s", computedDigest, expectedDigest)
24+
}
25+
return nil
26+
}
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
package image
2+
3+
import (
4+
"testing"
5+
6+
"github.com/opencontainers/go-digest"
7+
"github.com/stretchr/testify/assert"
8+
"github.com/stretchr/testify/require"
9+
)
10+
11+
func TestValidateBlobAgainstDigest(t *testing.T) {
12+
testBlob := []byte("test data")
13+
14+
tests := []struct {
15+
name string
16+
blob []byte
17+
expectedDigest digest.Digest
18+
expectError bool
19+
errorContains string
20+
}{
21+
{
22+
name: "empty digest",
23+
blob: testBlob,
24+
expectedDigest: "",
25+
expectError: true,
26+
errorContains: "expected digest is empty",
27+
},
28+
{
29+
name: "invalid digest format - no algorithm",
30+
blob: testBlob,
31+
expectedDigest: "invalidsyntax",
32+
expectError: true,
33+
errorContains: "invalid digest format",
34+
},
35+
{
36+
name: "invalid digest format - sha256 prefix w/ malformed hex",
37+
blob: testBlob,
38+
expectedDigest: "sha256:notahexstring!@#",
39+
expectError: true,
40+
errorContains: "invalid digest format",
41+
},
42+
{
43+
name: "invalid digest format - sha512 prefix w/ malformed hex",
44+
blob: testBlob,
45+
expectedDigest: "sha512:notahexstring!@#",
46+
expectError: true,
47+
errorContains: "invalid digest format",
48+
},
49+
{
50+
name: "invalid digest format - unknown algorithm",
51+
blob: testBlob,
52+
expectedDigest: "unknown-algo:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
53+
expectError: true,
54+
errorContains: "invalid digest format",
55+
},
56+
{
57+
name: "digest mismatch",
58+
blob: testBlob,
59+
expectedDigest: digest.SHA256.FromBytes([]byte("different data")),
60+
expectError: true,
61+
errorContains: "blob digest",
62+
},
63+
{
64+
name: "empty blob with matching digest",
65+
blob: []byte{},
66+
expectedDigest: digest.SHA256.FromBytes([]byte{}),
67+
expectError: false,
68+
},
69+
{
70+
name: "unavailable algorithm - blake2b",
71+
blob: testBlob,
72+
expectedDigest: "blake2b:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
73+
expectError: true,
74+
errorContains: "invalid digest format",
75+
},
76+
{
77+
name: "sha256 digest success",
78+
blob: testBlob,
79+
expectedDigest: digest.SHA256.FromBytes(testBlob),
80+
expectError: false,
81+
},
82+
{
83+
name: "sha512 digest success",
84+
blob: testBlob,
85+
expectedDigest: digest.SHA512.FromBytes(testBlob),
86+
expectError: false,
87+
},
88+
}
89+
90+
for _, tt := range tests {
91+
t.Run(tt.name, func(t *testing.T) {
92+
err := validateBlobAgainstDigest(tt.blob, tt.expectedDigest)
93+
if tt.expectError {
94+
require.Error(t, err)
95+
assert.Contains(t, err.Error(), tt.errorContains)
96+
} else {
97+
assert.NoError(t, err)
98+
}
99+
})
100+
}
101+
}

image/internal/image/docker_schema2.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -110,9 +110,8 @@ func (m *manifestSchema2) ConfigBlob(ctx context.Context) ([]byte, error) {
110110
if err != nil {
111111
return nil, err
112112
}
113-
computedDigest := digest.FromBytes(blob)
114-
if computedDigest != m.m.ConfigDescriptor.Digest {
115-
return nil, fmt.Errorf("Download config.json digest %s does not match expected %s", computedDigest, m.m.ConfigDescriptor.Digest)
113+
if err := validateBlobAgainstDigest(blob, m.m.ConfigDescriptor.Digest); err != nil {
114+
return nil, fmt.Errorf("config validation failed: %w", err)
116115
}
117116
m.configBlob = blob
118117
}

image/internal/image/oci.go

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import (
88
"slices"
99

1010
ociencspec "github.com/containers/ocicrypt/spec"
11-
"github.com/opencontainers/go-digest"
1211
imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
1312
"go.podman.io/image/v5/docker/reference"
1413
"go.podman.io/image/v5/internal/iolimits"
@@ -74,9 +73,8 @@ func (m *manifestOCI1) ConfigBlob(ctx context.Context) ([]byte, error) {
7473
if err != nil {
7574
return nil, err
7675
}
77-
computedDigest := digest.FromBytes(blob)
78-
if computedDigest != m.m.Config.Digest {
79-
return nil, fmt.Errorf("Download config.json digest %s does not match expected %s", computedDigest, m.m.Config.Digest)
76+
if err := validateBlobAgainstDigest(blob, m.m.Config.Digest); err != nil {
77+
return nil, fmt.Errorf("config validation failed: %w", err)
8078
}
8179
m.configBlob = blob
8280
}

0 commit comments

Comments
 (0)