@@ -23,6 +23,7 @@ import (
23
23
"fmt"
24
24
"hash"
25
25
"io"
26
+ "slices"
26
27
27
28
in_toto "github.com/in-toto/attestation/go/v1"
28
29
"github.com/secure-systems-lab/go-securesystemslib/dsse"
@@ -146,56 +147,43 @@ func verifyEnvelopeWithArtifact(verifier signature.Verifier, envelope EnvelopeCo
146
147
if err = limitSubjects (statement ); err != nil {
147
148
return err
148
149
}
149
-
150
- var artifactDigestAlgorithm string
151
- var artifactDigest []byte
152
-
153
- // Determine artifact digest algorithm by looking at the first subject's
154
- // digests. This assumes that if a statement contains multiple subjects,
155
- // they all use the same digest algorithm(s).
150
+ // Sanity check (no subjects)
156
151
if len (statement .Subject ) == 0 {
157
152
return errors .New ("no subjects found in statement" )
158
153
}
159
- if len (statement .Subject [0 ].Digest ) == 0 {
160
- return errors .New ("no digests found in statement" )
161
- }
162
154
163
- // Select the strongest digest algorithm available.
164
- for _ , alg := range []string {"sha512" , "sha384" , "sha256" } {
165
- if _ , ok := statement .Subject [0 ].Digest [alg ]; ok {
166
- artifactDigestAlgorithm = alg
167
- continue
168
- }
169
- }
170
- if artifactDigestAlgorithm == "" {
171
- return errors .New ("could not verify artifact: unsupported digest algorithm" )
155
+ // determine which hash functions to use
156
+ hashFuncs , err := getHashFunctions (statement )
157
+ if err != nil {
158
+ return fmt .Errorf ("unable to determine hash functions: %w" , err )
172
159
}
173
160
174
161
// Compute digest of the artifact.
175
- var hasher hash.Hash
176
- switch artifactDigestAlgorithm {
177
- case "sha512" :
178
- hasher = crypto .SHA512 .New ()
179
- case "sha384" :
180
- hasher = crypto .SHA384 .New ()
181
- case "sha256" :
182
- hasher = crypto .SHA256 .New ()
162
+ hasher , err := newMultihasher (hashFuncs )
163
+ if err != nil {
164
+ return fmt .Errorf ("could not verify artifact: unable to create hasher: %w" , err )
183
165
}
184
166
_ , err = io .Copy (hasher , artifact )
185
167
if err != nil {
186
168
return fmt .Errorf ("could not verify artifact: unable to calculate digest: %w" , err )
187
169
}
188
- artifactDigest = hasher .Sum (nil )
170
+ artifactDigests : = hasher .Sum (nil )
189
171
190
172
// Look for artifact digest in statement
191
173
for _ , subject := range statement .Subject {
192
- for alg , digest := range subject .Digest {
193
- hexdigest , err := hex . DecodeString ( digest )
174
+ for alg , hexdigest := range subject .Digest {
175
+ hf , err := algStringToHashFunc ( alg )
194
176
if err != nil {
195
- return fmt . Errorf ( "could not verify artifact: unable to decode subject digest: %w" , err )
177
+ continue
196
178
}
197
- if alg == artifactDigestAlgorithm && bytes .Equal (artifactDigest , hexdigest ) {
198
- return nil
179
+ if artifactDigest , ok := artifactDigests [hf ]; ok {
180
+ digest , err := hex .DecodeString (hexdigest )
181
+ if err != nil {
182
+ continue
183
+ }
184
+ if bytes .Equal (artifactDigest , digest ) {
185
+ return nil
186
+ }
199
187
}
200
188
}
201
189
}
@@ -269,3 +257,111 @@ func limitSubjects(statement *in_toto.Statement) error {
269
257
}
270
258
return nil
271
259
}
260
+
261
+ type multihasher struct {
262
+ hashfuncs []crypto.Hash
263
+ hashes []hash.Hash
264
+ }
265
+
266
+ func newMultihasher (hashfuncs []crypto.Hash ) (* multihasher , error ) {
267
+ if len (hashfuncs ) == 0 {
268
+ return nil , errors .New ("no hash functions specified" )
269
+ }
270
+ hashes := make ([]hash.Hash , len (hashfuncs ))
271
+ for i := range hashfuncs {
272
+ hashes [i ] = hashfuncs [i ].New ()
273
+ }
274
+ return & multihasher {
275
+ hashfuncs : hashfuncs ,
276
+ hashes : hashes ,
277
+ }, nil
278
+ }
279
+
280
+ func (m * multihasher ) Write (p []byte ) (n int , err error ) {
281
+ for i := range m .hashes {
282
+ n , err = m .hashes [i ].Write (p )
283
+ if err != nil {
284
+ return
285
+ }
286
+ }
287
+ return
288
+ }
289
+
290
+ func (m * multihasher ) Sum (b []byte ) map [crypto.Hash ][]byte {
291
+ sums := make (map [crypto.Hash ][]byte , len (m .hashes ))
292
+ for i := range m .hashes {
293
+ sums [m.hashfuncs [i ]] = m .hashes [i ].Sum (b )
294
+ }
295
+ return sums
296
+ }
297
+
298
+ func algStringToHashFunc (alg string ) (crypto.Hash , error ) {
299
+ switch alg {
300
+ case "sha256" :
301
+ return crypto .SHA256 , nil
302
+ case "sha384" :
303
+ return crypto .SHA384 , nil
304
+ case "sha512" :
305
+ return crypto .SHA512 , nil
306
+ default :
307
+ return 0 , errors .New ("unsupported digest algorithm" )
308
+ }
309
+ }
310
+
311
+ // getHashFunctions returns the smallest subset of supported hash functions
312
+ // that are needed to verify all subjects in a statement.
313
+ func getHashFunctions (statement * in_toto.Statement ) ([]crypto.Hash , error ) {
314
+ if len (statement .Subject ) == 0 {
315
+ return nil , errors .New ("no subjects found in statement" )
316
+ }
317
+
318
+ supportedHashFuncs := []crypto.Hash {crypto .SHA512 , crypto .SHA384 , crypto .SHA256 }
319
+ chosenHashFuncs := make ([]crypto.Hash , 0 , len (supportedHashFuncs ))
320
+ subjectHashFuncs := make ([][]crypto.Hash , len (statement .Subject ))
321
+
322
+ // go through the statement and make a simple data structure to hold the
323
+ // list of hash funcs for each subject (subjectHashFuncs)
324
+ for i , subject := range statement .Subject {
325
+ for alg := range subject .Digest {
326
+ hf , err := algStringToHashFunc (alg )
327
+ if err != nil {
328
+ continue
329
+ }
330
+ subjectHashFuncs [i ] = append (subjectHashFuncs [i ], hf )
331
+ }
332
+ }
333
+
334
+ // for each subject, see if we have chosen a compatible hash func, and if
335
+ // not, add the first one that is supported
336
+ for _ , hfs := range subjectHashFuncs {
337
+ // if any of the hash funcs are already in chosenHashFuncs, skip
338
+ if len (intersection (hfs , chosenHashFuncs )) > 0 {
339
+ continue
340
+ }
341
+
342
+ // check each supported hash func and add it if the subject
343
+ // has a digest for it
344
+ for _ , hf := range supportedHashFuncs {
345
+ if slices .Contains (hfs , hf ) {
346
+ chosenHashFuncs = append (chosenHashFuncs , hf )
347
+ break
348
+ }
349
+ }
350
+ }
351
+
352
+ if len (chosenHashFuncs ) == 0 {
353
+ return nil , errors .New ("no supported digest algorithms found" )
354
+ }
355
+
356
+ return chosenHashFuncs , nil
357
+ }
358
+
359
+ func intersection (a , b []crypto.Hash ) []crypto.Hash {
360
+ var result []crypto.Hash
361
+ for _ , x := range a {
362
+ if slices .Contains (b , x ) {
363
+ result = append (result , x )
364
+ }
365
+ }
366
+ return result
367
+ }
0 commit comments