-
Notifications
You must be signed in to change notification settings - Fork 24
/
metadata.go
268 lines (223 loc) · 7.46 KB
/
metadata.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
// Copyright (c) 2020-2021, Sylabs Inc. All rights reserved.
// This software is licensed under a 3-clause BSD license. Please consult the LICENSE.md file
// distributed with the sources of this project regarding your rights to use or distribute this
// software.
package integrity
import (
"crypto"
"errors"
"fmt"
"io"
"github.com/hpcng/sif/v2/pkg/sif"
)
var (
errObjectNotSigned = errors.New("object not signed")
errSignedObjectNotFound = errors.New("signed object not found")
errMinimumIDInvalid = errors.New("minimum ID value invalid")
)
// ErrHeaderIntegrity is the error returned when the integrity of the SIF global header is
// compromised.
var ErrHeaderIntegrity = errors.New("header integrity compromised")
// DescriptorIntegrityError records an error in cryptographic verification of a data object
// descriptor.
type DescriptorIntegrityError struct {
ID uint32 // Data object ID.
}
func (e *DescriptorIntegrityError) Error() string {
if e.ID == 0 {
return "data object descriptor integrity compromised"
}
return fmt.Sprintf("data object descriptor integrity compromised: %v", e.ID)
}
// Is compares e against target. If target is a DescriptorIntegrityError and matches e or target
// has a zero value ID, true is returned.
func (e *DescriptorIntegrityError) Is(target error) bool {
t, ok := target.(*DescriptorIntegrityError)
if !ok {
return false
}
return e.ID == t.ID || t.ID == 0
}
// ObjectIntegrityError records an error in cryptographic verification of a data object.
type ObjectIntegrityError struct {
ID uint32 // Data object ID.
}
func (e *ObjectIntegrityError) Error() string {
if e.ID == 0 {
return "data object integrity compromised"
}
return fmt.Sprintf("data object integrity compromised: %v", e.ID)
}
// Is compares e against target. If target is a ObjectIntegrityError and matches e or target has a
// zero value ID, true is returned.
func (e *ObjectIntegrityError) Is(target error) bool {
t, ok := target.(*ObjectIntegrityError)
if !ok {
return false
}
return e.ID == t.ID || t.ID == 0
}
type headerMetadata struct {
Digest digest `json:"digest"`
}
// getHeaderMetadata returns headerMetadata for the fields read from r, using hash algorithm h.
func getHeaderMetadata(r io.Reader, h crypto.Hash) (headerMetadata, error) {
d, err := newDigestReader(h, r)
if err != nil {
return headerMetadata{}, err
}
return headerMetadata{Digest: d}, nil
}
// matches verifies the fields read fromr matche the metadata in hm.
//
// If the SIF global header does not match, ErrHeaderIntegrity is returned.
func (hm headerMetadata) matches(r io.Reader) error {
if ok, err := hm.Digest.matches(r); err != nil {
return err
} else if !ok {
return ErrHeaderIntegrity
}
return nil
}
type objectMetadata struct {
RelativeID uint32 `json:"relativeId"`
DescriptorDigest digest `json:"descriptorDigest"`
ObjectDigest digest `json:"objectDigest"`
id uint32 // absolute object ID (minID + RelativeID)
}
// getObjectMetadata returns objectMetadata for object with relativeID, using digests calculated
// over descr and data using hash algorithm h.
func getObjectMetadata(relativeID uint32, descr, data io.Reader, h crypto.Hash) (objectMetadata, error) {
om := objectMetadata{RelativeID: relativeID}
// Calculate digest on object descriptor.
d, err := newDigestReader(h, descr)
if err != nil {
return objectMetadata{}, err
}
om.DescriptorDigest = d
// Calculate digest on object data.
d, err = newDigestReader(h, data)
if err != nil {
return objectMetadata{}, err
}
om.ObjectDigest = d
return om, nil
}
// populateAbsoluteID populates the absolute object ID of om based on minID.
func (om *objectMetadata) populateAbsoluteID(minID uint32) {
om.id = minID + om.RelativeID
}
// matches verifies the object described by od matches the metadata in om.
//
// If the data object descriptor does not match, a DescriptorIntegrityError is returned. If the
// data object does not match, a ObjectIntegrityError is returned.
func (om objectMetadata) matches(od sif.Descriptor) error {
if ok, err := om.DescriptorDigest.matches(od.GetIntegrityReader()); err != nil {
return err
} else if !ok {
return &DescriptorIntegrityError{ID: od.ID()}
}
if ok, err := om.ObjectDigest.matches(od.GetReader()); err != nil {
return err
} else if !ok {
return &ObjectIntegrityError{ID: od.ID()}
}
return nil
}
type mdVersion int
const (
metadataVersion1 mdVersion = iota + 1
)
type imageMetadata struct {
Version mdVersion `json:"version"`
Header headerMetadata `json:"header"`
Objects []objectMetadata `json:"objects"`
}
// getImageMetadata returns populated imageMetadata for object descriptors ods in f, using hash
// algorithm h.
func getImageMetadata(f *sif.FileImage, minID uint32, ods []sif.Descriptor, h crypto.Hash) (imageMetadata, error) {
im := imageMetadata{Version: metadataVersion1}
// Add header metadata.
hm, err := getHeaderMetadata(f.GetHeaderIntegrityReader(), h)
if err != nil {
return imageMetadata{}, err
}
im.Header = hm
// Add object descriptor/data metadata.
for _, od := range ods {
id := od.ID()
if id < minID { // shouldn't really be possible...
return imageMetadata{}, errMinimumIDInvalid
}
om, err := getObjectMetadata(id-minID, od.GetIntegrityReader(), od.GetReader(), h)
if err != nil {
return imageMetadata{}, err
}
im.Objects = append(im.Objects, om)
}
im.populateAbsoluteObjectIDs(minID)
return im, nil
}
// populateAbsoluteObjectIDs populates the absolute object ID of each object in im by adding minID
// to the relative ID of each object in im.
func (im *imageMetadata) populateAbsoluteObjectIDs(minID uint32) {
for i := range im.Objects {
im.Objects[i].populateAbsoluteID(minID)
}
}
// objectIDsMatch verifies the object IDs described by ods match exactly the object IDs described
// by im.
func (im imageMetadata) objectIDsMatch(ods []sif.Descriptor) error {
ids := make(map[uint32]bool)
for _, om := range im.Objects {
ids[om.id] = false
}
// Check each object in ods exists in ids, and mark as seen.
for _, od := range ods {
id := od.ID()
if _, ok := ids[id]; !ok {
return fmt.Errorf("object %d: %w", id, errObjectNotSigned)
}
ids[id] = true
}
// Check that all objects in ids were seen.
for id, seen := range ids {
if !seen {
return fmt.Errorf("object %d: %w", id, errSignedObjectNotFound)
}
}
return nil
}
// metadataForObject retrieves the objectMetadata for object specified by id.
func (im imageMetadata) metadataForObject(id uint32) (objectMetadata, error) {
for _, om := range im.Objects {
if om.id == id {
return om, nil
}
}
return objectMetadata{}, fmt.Errorf("object %d: %w", id, errObjectNotSigned)
}
// matches verifies the header and objects described by ods match the metadata in im.
//
// If the SIF global header does not match, ErrHeaderIntegrity is returned. If the data object
// descriptor does not match, a DescriptorIntegrityError is returned. If the data object does not
// match, a ObjectIntegrityError is returned.
func (im imageMetadata) matches(f *sif.FileImage, ods []sif.Descriptor) ([]sif.Descriptor, error) {
verified := make([]sif.Descriptor, 0, len(ods))
// Verify header metadata.
if err := im.Header.matches(f.GetHeaderIntegrityReader()); err != nil {
return verified, err
}
// Verify data object metadata.
for _, od := range ods {
om, err := im.metadataForObject(od.ID())
if err != nil {
return verified, err
}
if err := om.matches(od); err != nil {
return verified, err
}
verified = append(verified, od)
}
return verified, nil
}