@@ -400,27 +400,64 @@ func checkOneSum(mod module.Version, h string) {
400400 defer goSum .mu .Unlock ()
401401 if initGoSum () {
402402 checkOneSumLocked (mod , h )
403+ } else if useNotary (mod ) {
404+ checkNotarySum (mod , h )
403405 }
404406}
405407
406408func checkOneSumLocked (mod module.Version , h string ) {
407409 goSum .checked [modSum {mod , h }] = true
408410
409- for _ , vh := range goSum .m [mod ] {
410- if h == vh {
411- return
411+ checkGoSum := func () bool {
412+ for _ , vh := range goSum .m [mod ] {
413+ if h == vh {
414+ return true
415+ }
416+ if strings .HasPrefix (vh , "h1:" ) {
417+ base .Fatalf ("verifying %s@%s: checksum mismatch\n \t downloaded: %v\n \t go.sum: %v" + goSumMismatch , mod .Path , mod .Version , h , vh )
418+ }
412419 }
413- if strings .HasPrefix (vh , "h1:" ) {
414- base .Fatalf ("verifying %s@%s: checksum mismatch\n \t downloaded: %v\n \t go.sum: %v" , mod .Path , mod .Version , h , vh )
420+ return false
421+ }
422+
423+ if checkGoSum () {
424+ return
425+ }
426+
427+ if useNotary (mod ) {
428+ goSum .mu .Unlock ()
429+ checkNotarySum (mod , h ) // dies if h is wrong
430+ goSum .mu .Lock ()
431+
432+ // Because we dropped the lock, a racing goroutine
433+ // may have already added this entry to go.sum.
434+ // Check again.
435+ if checkGoSum () {
436+ return
415437 }
416438 }
439+
417440 if len (goSum .m [mod ]) > 0 {
418- fmt .Fprintf (os .Stderr , "warning: verifying %s@%s: unknown hashes in go.sum: %v; adding %v" , mod .Path , mod .Version , strings .Join (goSum .m [mod ], ", " ), h )
441+ fmt .Fprintf (os .Stderr , "warning: verifying %s@%s: unknown hashes in go.sum: %v; adding %v" + hashVersionMismatch , mod .Path , mod .Version , strings .Join (goSum .m [mod ], ", " ), h )
419442 }
420443 goSum .m [mod ] = append (goSum .m [mod ], h )
421444 goSum .dirty = true
422445}
423446
447+ // checkNotarySum checks the mod, h pair against the Go notary.
448+ // It calls base.Fatalf if the hash is to be rejected.
449+ func checkNotarySum (mod module.Version , h string ) {
450+ hashes := notaryHashes (mod )
451+ for _ , vh := range hashes {
452+ if h == vh {
453+ return
454+ }
455+ if strings .HasPrefix (vh , "h1:" ) {
456+ base .Fatalf ("verifying %s@%s: checksum mismatch\n \t downloaded: %v\n \t notary: %v" + notarySumMismatch , mod .Path , mod .Version , h , vh )
457+ }
458+ }
459+ }
460+
424461// Sum returns the checksum for the downloaded copy of the given module,
425462// if present in the download cache.
426463func Sum (mod module.Version ) string {
@@ -539,3 +576,117 @@ func TrimGoSum(keep map[module.Version]bool) {
539576 }
540577 }
541578}
579+
580+ const goSumMismatch = `
581+
582+ SECURITY ERROR
583+ This download does NOT match an earlier download recorded in go.sum.
584+ The bits may have been replaced on the origin server, or an attacker may
585+ have intercepted the download attempt.
586+
587+ For more information, see 'go help module-auth'.
588+ `
589+
590+ const notarySumMismatch = `
591+
592+ SECURITY ERROR
593+ This download does NOT match the expected download known to the notary.
594+ The bits may have been replaced on the origin server, or an attacker may
595+ have intercepted the download attempt.
596+
597+ For more information, see 'go help module-auth'.
598+ `
599+
600+ const hashVersionMismatch = `
601+
602+ SECURITY WARNING
603+ This download is listed in go.sum, but using an unknown hash algorithm.
604+ The download cannot be verified.
605+
606+ For more information, see 'go help module-auth'.
607+
608+ `
609+
610+ var HelpSum = & base.Command {
611+ UsageLine : "module-auth" ,
612+ Short : "module authentication using go.sum" ,
613+ Long : `
614+ The go command tries to authenticate every downloaded module,
615+ checking that the bits downloaded for a specific module version today
616+ match bits downloaded yesterday. This ensures repeatable builds
617+ and detects introduction of unexpected changes, malicious or not.
618+
619+ In each module's root, alongside go.mod, the go command maintains
620+ a file named go.sum containing the cryptographic checksums of the
621+ module's dependencies.
622+
623+ The form of each line in go.sum is three fields:
624+
625+ <module> <version>[/go.mod] <hash>
626+
627+ Each known module version results in two lines in the go.sum file.
628+ The first line gives the hash of the module version's file tree.
629+ The second line appends "/go.mod" to the version and gives the hash
630+ of only the module version's (possibly synthesized) go.mod file.
631+ The go.mod-only hash allows downloading and authenticating a
632+ module version's go.mod file, which is needed to compute the
633+ dependency graph, without also downloading all the module's source code.
634+
635+ The hash begins with an algorithm prefix of the form "h<N>:".
636+ The only defined algorithm prefix is "h1:", which uses SHA-256.
637+
638+ Module authentication failures
639+
640+ The go command maintains a cache of downloaded packages and computes
641+ and records the cryptographic checksum of each package at download time.
642+ In normal operation, the go command checks the main module's go.sum file
643+ against these precomputed checksums instead of recomputing them on
644+ each command invocation. The 'go mod verify' command checks that
645+ the cached copies of module downloads still match both their recorded
646+ checksums and the entries in go.sum.
647+
648+ In day-to-day development, the checksum of a given module version
649+ should never change. Each time a dependency is used by a given main
650+ module, the go command checks its local cached copy, freshly
651+ downloaded or not, against the main module's go.sum. If the checksums
652+ don't match, the go command reports the mismatch as a security error
653+ and refuses to run the build. When this happens, proceed with caution:
654+ code changing unexpectedly means today's build will not match
655+ yesterday's, and the unexpected change may not be beneficial.
656+
657+ If the go command reports a mismatch in go.sum, the downloaded code
658+ for the reported module version does not match the one used in a
659+ previous build of the main module. It is important at that point
660+ to find out what the right checksum should be, to decide whether
661+ go.sum is wrong or the downloaded code is wrong. Usually go.sum is right:
662+ you want to use the same code you used yesterday.
663+
664+ If a downloaded module is not yet included in go.sum and it is a publicly
665+ available module, the go command consults the Go notary server to fetch
666+ the expected go.sum lines. If the downloaded code does not match those
667+ lines, the go command reports the mismatch and exits. Note that the
668+ notary is not consulted for module versions already listed in go.sum.
669+
670+ The GONOVERIFY environment variable is a comma-separated list of
671+ patterns (in the syntax of Go's path.Match) of module path prefixes
672+ that should not be verified using the notary. For example,
673+
674+ GONOVERIFY=*.corp.example.com,rsc.io/private
675+
676+ disables notary verification for modules with path prefixes matching
677+ either pattern, including "git.corp.example.com/xyzzy", "rsc.io/private",
678+ and "rsc.io/private/quux".
679+
680+ As a special case, if GONOVERIFY is set to "off", or if "go get" was invoked
681+ with the -insecure flag, the notary is never consulted, but note that this
682+ defeats the security provided by the notary. A better course of action is
683+ to set a narrower GONOVERIFY and, in the case of go.sum mismatches,
684+ investigate why the code downloaded code differs from what was
685+ downloaded yesterday.
686+
687+ NOTE: Early in the Go 1.13 dev cycle, the notary is being simulated by
688+ a whitelist of known hashes for popular Go modules, to expose any
689+ problems arising from knowing the expected hashes.
690+ TODO(rsc): This note should be removed once the real notary is used instead. See #30601.
691+ ` ,
692+ }
0 commit comments