@@ -44,6 +44,12 @@ import (
44
44
45
45
var signatureFileRegex = regexp .MustCompile (`^\.SIGN\.(DSA|RSA|RSA256|RSA512)\.(.*\.rsa\.pub)$` )
46
46
47
+ type Signature struct {
48
+ KeyID string
49
+ Signature []byte
50
+ DigestAlgorithm crypto.Hash
51
+ }
52
+
47
53
// This is terrible but simpler than plumbing around a cache for now.
48
54
// We just hold the parsed index in memory rather than re-parsing it every time,
49
55
// which requires gunzipping, which is (somewhat) expensive.
@@ -332,9 +338,11 @@ func fetchRepositoryIndex(ctx context.Context, u string, etag string, opts *inde
332
338
func parseRepositoryIndex (ctx context.Context , u string , keys map [string ][]byte , arch string , b []byte , opts * indexOpts ) (* APKIndex , error ) { //nolint:gocyclo
333
339
_ , span := otel .Tracer ("go-apk" ).Start (ctx , "parseRepositoryIndex" )
334
340
defer span .End ()
335
-
336
341
// validate the signature
337
342
if shouldCheckSignatureForIndex (u , arch , opts ) {
343
+ if len (keys ) == 0 {
344
+ return nil , fmt .Errorf ("no keys provided to verify signature" )
345
+ }
338
346
buf := bytes .NewReader (b )
339
347
gzipReader , err := gzip .NewReader (buf )
340
348
if err != nil {
@@ -348,14 +356,12 @@ func parseRepositoryIndex(ctx context.Context, u string, keys map[string][]byte,
348
356
349
357
tarReader := tar .NewReader (gzipReader )
350
358
351
- var keyfile string
352
- var signature []byte
353
- var indexDigestType crypto.Hash
359
+ sigs := make ([]Signature , 0 , len (keys ))
354
360
for {
355
361
// read the signature(s)
356
362
signatureFile , err := tarReader .Next ()
357
363
// found everything, end of stream
358
- if len ( keyfile ) > 0 && errors .Is (err , io .EOF ) {
364
+ if errors .Is (err , io .EOF ) {
359
365
break
360
366
}
361
367
// oops something went wrong
@@ -366,61 +372,67 @@ func parseRepositoryIndex(ctx context.Context, u string, keys map[string][]byte,
366
372
if len (matches ) != 3 {
367
373
return nil , fmt .Errorf ("failed to find key name in signature file name: %s" , signatureFile .Name )
368
374
}
369
- // It is lucky that golang only iterates over sorted file names, and that
370
- // lexically latest is the strongest hash
375
+ keyfile := matches [2 ]
376
+ if _ , ok := keys [keyfile ]; ! ok {
377
+ // Ignore this signature if we don't have the key
378
+ continue
379
+ }
380
+ var digestAlgorithm crypto.Hash
371
381
switch signatureType := matches [1 ]; signatureType {
372
382
case "DSA" :
373
383
// Obsolete
374
384
continue
375
385
case "RSA" :
376
386
// Current legacy compat
377
- indexDigestType = crypto .SHA1
387
+ digestAlgorithm = crypto .SHA1
378
388
case "RSA256" :
379
389
// Current best practice
380
- indexDigestType = crypto .SHA256
390
+ digestAlgorithm = crypto .SHA256
381
391
case "RSA512" :
382
392
// Too big, too slow, not compiled in
383
393
continue
384
394
default :
385
395
return nil , fmt .Errorf ("unknown signature format: %s" , signatureType )
386
396
}
387
- keyfile = matches [2 ]
388
- signature , err = io .ReadAll (tarReader )
397
+ signature , err := io .ReadAll (tarReader )
389
398
if err != nil {
390
399
return nil , fmt .Errorf ("failed to read signature from repository index: %w" , err )
391
400
}
401
+ sigs = append (sigs , Signature {
402
+ KeyID : keyfile ,
403
+ Signature : signature ,
404
+ DigestAlgorithm : digestAlgorithm ,
405
+ })
406
+ }
407
+ if len (sigs ) == 0 {
408
+ return nil , fmt .Errorf ("no signature with known key found in repository index" )
392
409
}
393
410
// we now have the signature bytes and name, get the contents of the rest;
394
411
// this should be everything else in the raw gzip file as is.
395
412
allBytes := len (b )
396
413
unreadBytes := buf .Len ()
397
414
readBytes := allBytes - unreadBytes
398
415
indexData := b [readBytes :]
399
- indexDigest , err := sign .HashData (indexData , indexDigestType )
400
- if err != nil {
401
- return nil , err
402
- }
403
- // now we can check the signature
404
- if keys == nil {
405
- return nil , fmt .Errorf ("no keys provided to verify signature" )
406
- }
407
- var verified bool
408
- keyData , ok := keys [keyfile ]
409
- if ok {
410
- if err := sign .RSAVerifyDigest (indexDigest , indexDigestType , signature , keyData ); err != nil {
411
- verified = false
412
- }
413
- }
414
- if ! verified {
415
- for _ , keyData := range keys {
416
- if err := sign .RSAVerifyDigest (indexDigest , indexDigestType , signature , keyData ); err == nil {
417
- verified = true
418
- break
416
+ indexDigest := make (map [crypto.Hash ][]byte , len (keys ))
417
+ verified := false
418
+ for _ , sig := range sigs {
419
+ // compute the digest if not already done
420
+ if _ , hasDigest := indexDigest [sig .DigestAlgorithm ]; ! hasDigest {
421
+ digest , err := sign .HashData (indexData , sig .DigestAlgorithm )
422
+ if err != nil {
423
+ return nil , fmt .Errorf ("failed to compute digest: %w" , err )
419
424
}
425
+ indexDigest [sig .DigestAlgorithm ] = digest
426
+ }
427
+ if err := sign .RSAVerifyDigest (indexDigest [sig .DigestAlgorithm ], sig .DigestAlgorithm , sig .Signature , keys [sig .KeyID ]); err == nil {
428
+ verified = true
429
+ break
430
+ } else {
431
+ clog .FromContext (ctx ).Warnf ("failed to verify signature for keyfile %s: %v" , sig .KeyID , err )
420
432
}
421
433
}
422
434
if ! verified {
423
- return nil , fmt . Errorf ( "no key found to verify signature for keyfile %s; tried all other keys as well" , keyfile )
435
+ return nil , errors . New ( "signature verification failed for repository index, for all provided keys" )
424
436
}
425
437
}
426
438
// with a valid signature, convert it to an ApkIndex
0 commit comments