1
2
3
4
5 package modfetch
6
7 import (
8 "archive/zip"
9 "bytes"
10 "cmd/go/internal/base"
11 "cmd/go/internal/cfg"
12 "cmd/go/internal/fsys"
13 "cmd/go/internal/gover"
14 "cmd/go/internal/lockedfile"
15 "cmd/go/internal/str"
16 "cmd/go/internal/trace"
17 "cmd/internal/par"
18 "cmd/internal/robustio"
19 "context"
20 "crypto/sha256"
21 "encoding/base64"
22 "errors"
23 "fmt"
24 "io"
25 "io/fs"
26 "os"
27 "path/filepath"
28 "sort"
29 "strings"
30 "sync"
31
32 "golang.org/x/mod/module"
33 "golang.org/x/mod/sumdb/dirhash"
34 modzip "golang.org/x/mod/zip"
35 )
36
37 var (
38 ErrToolchain = errors.New("internal error: invalid operation on toolchain module")
39 ErrFIPS140 = errors.New("golang.org/fips140 is bundled with the Go distribution and cannot be downloaded")
40 )
41
42
43
44
45 func (f *Fetcher) Download(ctx context.Context, mod module.Version) (dir string, err error) {
46 if gover.IsToolchain(mod.Path) {
47 return "", ErrToolchain
48 }
49 if mod.Path == "golang.org/fips140" {
50 return "", ErrFIPS140
51 }
52 if err := checkCacheDir(ctx); err != nil {
53 base.Fatal(err)
54 }
55
56
57 return f.downloadCache.Do(mod, func() (string, error) {
58 dir, err := f.download(ctx, mod)
59 if err != nil {
60 return "", err
61 }
62 f.checkMod(ctx, mod)
63
64
65 if data, err := os.ReadFile(filepath.Join(dir, "go.mod")); err == nil {
66 goVersion := gover.GoModLookup(data, "go")
67 if gover.Compare(goVersion, gover.Local()) > 0 {
68 return "", &gover.TooNewError{What: mod.String(), GoVersion: goVersion}
69 }
70 } else if !errors.Is(err, fs.ErrNotExist) {
71 return "", err
72 }
73
74 return dir, nil
75 })
76 }
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97 func (f *Fetcher) Unzip(ctx context.Context, mod module.Version, zipfile, ziphash string, verify func() error) (dir string, err error) {
98 if ziphash == "" {
99 return "", module.VersionError(mod, errors.New("internal error: Unzip called with empty module zip hash"))
100 }
101 if err := checkCacheDir(ctx); err != nil {
102 base.Fatal(err)
103 }
104
105 return f.downloadCache.Do(mod, func() (string, error) {
106 ctx, span := trace.StartSpan(ctx, "modfetch.Unzip "+mod.String())
107 defer span.Done()
108
109 dir, err = DownloadDir(ctx, mod)
110 if err == nil {
111
112 ok, err := haveZipHash(ctx, mod, ziphash)
113 if err != nil {
114 return "", err
115 }
116 if ok {
117 return dir, nil
118 }
119 } else if dir == "" || !errors.Is(err, fs.ErrNotExist) {
120 return "", err
121 }
122
123 return unzip(ctx, mod, zipfile, ziphash, verify)
124 })
125 }
126
127 func haveZipHash(ctx context.Context, mod module.Version, ziphash string) (bool, error) {
128 path, err := CachePath(ctx, mod, "ziphash")
129 if err != nil {
130 return false, err
131 }
132 data, err := lockedfile.Read(path)
133 if errors.Is(err, fs.ErrNotExist) {
134 return false, nil
135 }
136 if err != nil {
137 return false, err
138 }
139 return strings.TrimSpace(string(data)) == ziphash, nil
140 }
141
142 func writeZipHash(ctx context.Context, mod module.Version, ziphash string) error {
143 path, err := CachePath(ctx, mod, "ziphash")
144 if err != nil {
145 return err
146 }
147 if err := os.MkdirAll(filepath.Dir(path), 0o777); err != nil {
148 return err
149 }
150 return lockedfile.Write(path, strings.NewReader(ziphash), 0o666)
151 }
152
153 func (f *Fetcher) download(ctx context.Context, mod module.Version) (dir string, err error) {
154 ctx, span := trace.StartSpan(ctx, "modfetch.download "+mod.String())
155 defer span.Done()
156
157 dir, err = DownloadDir(ctx, mod)
158 if err == nil {
159
160 return dir, nil
161 } else if dir == "" || !errors.Is(err, fs.ErrNotExist) {
162 return "", err
163 }
164
165
166
167
168 zipfile, err := f.DownloadZip(ctx, mod)
169 if err != nil {
170 return "", err
171 }
172
173 return unzip(ctx, mod, zipfile, "", nil)
174 }
175
176
177
178
179
180
181
182 func unzip(ctx context.Context, mod module.Version, zipfile, ziphash string, verify func() error) (dir string, err error) {
183 unlock, err := lockVersion(ctx, mod)
184 if err != nil {
185 return "", err
186 }
187 defer unlock()
188
189 ctx, span := trace.StartSpan(ctx, "unzip "+zipfile)
190 defer span.Done()
191
192
193 dir, dirErr := DownloadDir(ctx, mod)
194 if dirErr == nil {
195 if ziphash == "" {
196 return dir, nil
197 }
198 ok, err := haveZipHash(ctx, mod, ziphash)
199 if err != nil {
200 return "", err
201 }
202 if ok {
203 return dir, nil
204 }
205 dirErr = &DownloadDirPartialError{dir, errors.New("ziphash file does not match")}
206 }
207 _, dirExists := dirErr.(*DownloadDirPartialError)
208
209
210
211
212 if verify != nil {
213 if err := verify(); err != nil {
214 return "", err
215 }
216 }
217
218
219
220
221
222
223 parentDir := filepath.Dir(dir)
224 tmpPrefix := filepath.Base(dir) + ".tmp-"
225 if old, err := filepath.Glob(filepath.Join(str.QuoteGlob(parentDir), str.QuoteGlob(tmpPrefix)+"*")); err == nil {
226 for _, path := range old {
227 RemoveAll(path)
228 }
229 }
230 if dirExists {
231 if err := RemoveAll(dir); err != nil {
232 return "", err
233 }
234 }
235
236
237
238
239 if ziphash != "" {
240 hashPath, err := CachePath(ctx, mod, "ziphash")
241 if err != nil {
242 return "", err
243 }
244 if err := robustio.RemoveAll(hashPath); err != nil {
245 return "", err
246 }
247 }
248
249 partialPath, err := CachePath(ctx, mod, "partial")
250 if err != nil {
251 return "", err
252 }
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268 if err := os.MkdirAll(parentDir, 0o777); err != nil {
269 return "", err
270 }
271 if err := os.WriteFile(partialPath, nil, 0o666); err != nil {
272 return "", err
273 }
274 if err := modzip.Unzip(dir, mod, zipfile); err != nil {
275 fmt.Fprintf(os.Stderr, "-> %s\n", err)
276 if rmErr := RemoveAll(dir); rmErr == nil {
277 os.Remove(partialPath)
278 }
279 return "", err
280 }
281 if err := os.Remove(partialPath); err != nil {
282 return "", err
283 }
284
285
286 if ziphash != "" {
287 if err := writeZipHash(ctx, mod, ziphash); err != nil {
288 return "", err
289 }
290 }
291
292 if !cfg.ModCacheRW {
293 makeDirsReadOnly(dir)
294 }
295 return dir, nil
296 }
297
298 var downloadZipCache par.ErrCache[module.Version, string]
299
300
301
302 func (f *Fetcher) DownloadZip(ctx context.Context, mod module.Version) (zipfile string, err error) {
303 if mod.Path == "golang.org/fips140" {
304 return "", ErrFIPS140
305 }
306
307
308 return downloadZipCache.Do(mod, func() (string, error) {
309 zipfile, err := CachePath(ctx, mod, "zip")
310 if err != nil {
311 return "", err
312 }
313 ziphashfile := zipfile + "hash"
314
315
316 if _, err := os.Stat(zipfile); err == nil {
317 if _, err := os.Stat(ziphashfile); err == nil {
318 if !HaveSum(f, mod) {
319 f.checkMod(ctx, mod)
320 }
321 return zipfile, nil
322 }
323 }
324
325
326 if cfg.CmdName != "mod download" {
327 vers := mod.Version
328 if mod.Path == "golang.org/toolchain" {
329
330 _, vers, _ = strings.Cut(vers, "-")
331 if i := strings.LastIndex(vers, "."); i >= 0 {
332 goos, goarch, _ := strings.Cut(vers[i+1:], "-")
333 vers = vers[:i] + " (" + goos + "/" + goarch + ")"
334 }
335 fmt.Fprintf(os.Stderr, "go: downloading %s\n", vers)
336 } else {
337 fmt.Fprintf(os.Stderr, "go: downloading %s %s\n", mod.Path, vers)
338 }
339 }
340 unlock, err := lockVersion(ctx, mod)
341 if err != nil {
342 return "", err
343 }
344 defer unlock()
345
346 if err := f.downloadZip(ctx, mod, zipfile); err != nil {
347 return "", err
348 }
349 return zipfile, nil
350 })
351 }
352
353 func (f *Fetcher) downloadZip(ctx context.Context, mod module.Version, zipfile string) (err error) {
354 ctx, span := trace.StartSpan(ctx, "modfetch.downloadZip "+zipfile)
355 defer span.Done()
356
357
358
359 ziphashfile := zipfile + "hash"
360 var zipExists, ziphashExists bool
361 if _, err := os.Stat(zipfile); err == nil {
362 zipExists = true
363 }
364 if _, err := os.Stat(ziphashfile); err == nil {
365 ziphashExists = true
366 }
367 if zipExists && ziphashExists {
368 return nil
369 }
370
371
372 if err := os.MkdirAll(filepath.Dir(zipfile), 0o777); err != nil {
373 return err
374 }
375
376
377
378
379 tmpPattern := filepath.Base(zipfile) + "*.tmp"
380 if old, err := filepath.Glob(filepath.Join(str.QuoteGlob(filepath.Dir(zipfile)), tmpPattern)); err == nil {
381 for _, path := range old {
382 os.Remove(path)
383 }
384 }
385
386
387
388 if zipExists {
389 return hashZip(f, mod, zipfile, ziphashfile)
390 }
391
392
393
394
395
396
397 file, err := tempFile(ctx, filepath.Dir(zipfile), filepath.Base(zipfile), 0o666)
398 if err != nil {
399 return err
400 }
401 defer func() {
402 if err != nil {
403 file.Close()
404 os.Remove(file.Name())
405 }
406 }()
407
408 var unrecoverableErr error
409 err = TryProxies(func(proxy string) error {
410 if unrecoverableErr != nil {
411 return unrecoverableErr
412 }
413 repo := f.Lookup(ctx, proxy, mod.Path)
414 err := repo.Zip(ctx, file, mod.Version)
415 if err != nil {
416
417
418
419
420 if _, err := file.Seek(0, io.SeekStart); err != nil {
421 unrecoverableErr = err
422 return err
423 }
424 if err := file.Truncate(0); err != nil {
425 unrecoverableErr = err
426 return err
427 }
428 }
429 return err
430 })
431 if err != nil {
432 return err
433 }
434
435
436
437
438 fi, err := file.Stat()
439 if err != nil {
440 return err
441 }
442 z, err := zip.NewReader(file, fi.Size())
443 if err != nil {
444 return err
445 }
446 prefix := mod.Path + "@" + mod.Version + "/"
447 for _, zf := range z.File {
448 if !strings.HasPrefix(zf.Name, prefix) {
449 return fmt.Errorf("zip for %s has unexpected file %s", prefix[:len(prefix)-1], zf.Name)
450 }
451 }
452
453 if err := file.Close(); err != nil {
454 return err
455 }
456
457
458 if err := hashZip(f, mod, file.Name(), ziphashfile); err != nil {
459 return err
460 }
461 if err := os.Rename(file.Name(), zipfile); err != nil {
462 return err
463 }
464
465
466
467 return nil
468 }
469
470
471
472
473
474
475 func hashZip(f *Fetcher, mod module.Version, zipfile, ziphashfile string) (err error) {
476 hash, err := dirhash.HashZip(zipfile, dirhash.DefaultHash)
477 if err != nil {
478 return err
479 }
480 if err := checkModSum(f, mod, hash); err != nil {
481 return err
482 }
483 hf, err := lockedfile.Create(ziphashfile)
484 if err != nil {
485 return err
486 }
487 defer func() {
488 if closeErr := hf.Close(); err == nil && closeErr != nil {
489 err = closeErr
490 }
491 }()
492 if err := hf.Truncate(int64(len(hash))); err != nil {
493 return err
494 }
495 if _, err := hf.WriteAt([]byte(hash), 0); err != nil {
496 return err
497 }
498 return nil
499 }
500
501
502
503 func makeDirsReadOnly(dir string) {
504 type pathMode struct {
505 path string
506 mode fs.FileMode
507 }
508 var dirs []pathMode
509 filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
510 if err == nil && d.IsDir() {
511 info, err := d.Info()
512 if err == nil && info.Mode()&0o222 != 0 {
513 dirs = append(dirs, pathMode{path, info.Mode()})
514 }
515 }
516 return nil
517 })
518
519
520 for i := len(dirs) - 1; i >= 0; i-- {
521 os.Chmod(dirs[i].path, dirs[i].mode&^0o222)
522 }
523 }
524
525
526
527 func RemoveAll(dir string) error {
528
529 filepath.WalkDir(dir, func(path string, info fs.DirEntry, err error) error {
530 if err != nil {
531 return nil
532 }
533 if info.IsDir() {
534 os.Chmod(path, 0o777)
535 }
536 return nil
537 })
538 return robustio.RemoveAll(dir)
539 }
540
541
542
543
544
545 type modSum struct {
546 mod module.Version
547 sum string
548 }
549
550 type sumState struct {
551 m map[module.Version][]string
552 w map[string]map[module.Version][]string
553 status map[modSum]modSumStatus
554 overwrite bool
555 enabled bool
556 }
557
558 type modSumStatus struct {
559 used, dirty bool
560 }
561
562
563 type Fetcher struct {
564
565 goSumFile string
566
567 workspaceGoSumFiles []string
568
569
570
571 lookupCache *par.Cache[lookupCacheKey, Repo]
572
573
574
575
576
577 downloadCache *par.ErrCache[module.Version, string]
578
579 mu sync.Mutex
580 sumState sumState
581 }
582
583 func NewFetcher() *Fetcher {
584 f := new(Fetcher)
585 f.lookupCache = new(par.Cache[lookupCacheKey, Repo])
586 f.downloadCache = new(par.ErrCache[module.Version, string])
587 return f
588 }
589
590 func (f *Fetcher) GoSumFile() string {
591 return f.goSumFile
592 }
593
594 func (f *Fetcher) SetGoSumFile(str string) {
595 f.goSumFile = str
596 }
597
598 func (f *Fetcher) AddWorkspaceGoSumFile(file string) {
599 f.workspaceGoSumFiles = append(f.workspaceGoSumFiles, file)
600 }
601
602
603
604 func (f *Fetcher) Reset() {
605 f.SetState(NewFetcher())
606 }
607
608
609
610
611
612 func (f *Fetcher) SetState(newState *Fetcher) (oldState *Fetcher) {
613 if newState.lookupCache == nil {
614 newState.lookupCache = new(par.Cache[lookupCacheKey, Repo])
615 }
616 if newState.downloadCache == nil {
617 newState.downloadCache = new(par.ErrCache[module.Version, string])
618 }
619
620 f.mu.Lock()
621 defer f.mu.Unlock()
622
623 oldState = &Fetcher{
624 goSumFile: f.goSumFile,
625 workspaceGoSumFiles: f.workspaceGoSumFiles,
626 lookupCache: f.lookupCache,
627 downloadCache: f.downloadCache,
628 sumState: f.sumState,
629 }
630
631 f.SetGoSumFile(newState.goSumFile)
632 f.workspaceGoSumFiles = newState.workspaceGoSumFiles
633
634
635
636 f.lookupCache = newState.lookupCache
637 f.downloadCache = newState.downloadCache
638
639 f.sumState = newState.sumState
640
641 return oldState
642 }
643
644
645
646
647
648 func (f *Fetcher) initGoSum() (bool, error) {
649 if f.goSumFile == "" {
650 return false, nil
651 }
652 if f.sumState.m != nil {
653 return true, nil
654 }
655
656 f.sumState.m = make(map[module.Version][]string)
657 f.sumState.status = make(map[modSum]modSumStatus)
658 f.sumState.w = make(map[string]map[module.Version][]string)
659
660 for _, fn := range f.workspaceGoSumFiles {
661 f.sumState.w[fn] = make(map[module.Version][]string)
662 _, err := readGoSumFile(f.sumState.w[fn], fn)
663 if err != nil {
664 return false, err
665 }
666 }
667
668 enabled, err := readGoSumFile(f.sumState.m, f.goSumFile)
669 f.sumState.enabled = enabled
670 return enabled, err
671 }
672
673 func readGoSumFile(dst map[module.Version][]string, file string) (bool, error) {
674 var (
675 data []byte
676 err error
677 )
678 if fsys.Replaced(file) {
679
680
681
682 data, err = os.ReadFile(fsys.Actual(file))
683 } else {
684 data, err = lockedfile.Read(file)
685 }
686 if err != nil && !os.IsNotExist(err) {
687 return false, err
688 }
689 readGoSum(dst, file, data)
690
691 return true, nil
692 }
693
694
695
696
697 const emptyGoModHash = "h1:G7mAYYxgmS0lVkHyy2hEOLQCFB0DlQFTMLWggykrydY="
698
699
700
701 func readGoSum(dst map[module.Version][]string, file string, data []byte) {
702 lineno := 0
703 for len(data) > 0 {
704 var line []byte
705 lineno++
706 i := bytes.IndexByte(data, '\n')
707 if i < 0 {
708 line, data = data, nil
709 } else {
710 line, data = data[:i], data[i+1:]
711 }
712 f := strings.Fields(string(line))
713 if len(f) == 0 {
714
715 continue
716 }
717 if len(f) != 3 {
718 if cfg.CmdName == "mod tidy" {
719
720 continue
721 } else {
722 base.Fatalf("malformed go.sum:\n%s:%d: wrong number of fields %v\n", file, lineno, len(f))
723 }
724 }
725 if f[2] == emptyGoModHash {
726
727 continue
728 }
729 mod := module.Version{Path: f[0], Version: f[1]}
730 dst[mod] = append(dst[mod], f[2])
731 }
732 }
733
734
735
736
737
738 func HaveSum(f *Fetcher, mod module.Version) bool {
739 f.mu.Lock()
740 defer f.mu.Unlock()
741 inited, err := f.initGoSum()
742 if err != nil || !inited {
743 return false
744 }
745 for _, goSums := range f.sumState.w {
746 for _, h := range goSums[mod] {
747 if !strings.HasPrefix(h, "h1:") {
748 continue
749 }
750 if !f.sumState.status[modSum{mod, h}].dirty {
751 return true
752 }
753 }
754 }
755 for _, h := range f.sumState.m[mod] {
756 if !strings.HasPrefix(h, "h1:") {
757 continue
758 }
759 if !f.sumState.status[modSum{mod, h}].dirty {
760 return true
761 }
762 }
763 return false
764 }
765
766
767
768
769
770
771
772 func (f *Fetcher) RecordedSum(mod module.Version) (sum string, ok bool) {
773 f.mu.Lock()
774 defer f.mu.Unlock()
775 inited, err := f.initGoSum()
776 foundSum := ""
777 if err != nil || !inited {
778 return "", false
779 }
780 for _, goSums := range f.sumState.w {
781 for _, h := range goSums[mod] {
782 if !strings.HasPrefix(h, "h1:") {
783 continue
784 }
785 if !f.sumState.status[modSum{mod, h}].dirty {
786 if foundSum != "" && foundSum != h {
787 return "", false
788 }
789 foundSum = h
790 }
791 }
792 }
793 for _, h := range f.sumState.m[mod] {
794 if !strings.HasPrefix(h, "h1:") {
795 continue
796 }
797 if !f.sumState.status[modSum{mod, h}].dirty {
798 if foundSum != "" && foundSum != h {
799 return "", false
800 }
801 foundSum = h
802 }
803 }
804 return foundSum, true
805 }
806
807
808 func (f *Fetcher) checkMod(ctx context.Context, mod module.Version) {
809
810 ziphash, err := CachePath(ctx, mod, "ziphash")
811 if err != nil {
812 base.Fatalf("verifying %v", module.VersionError(mod, err))
813 }
814 data, err := lockedfile.Read(ziphash)
815 if err != nil {
816 base.Fatalf("verifying %v", module.VersionError(mod, err))
817 }
818 data = bytes.TrimSpace(data)
819 if !isValidSum(data) {
820
821 zip, err := CachePath(ctx, mod, "zip")
822 if err != nil {
823 base.Fatalf("verifying %v", module.VersionError(mod, err))
824 }
825 err = hashZip(f, mod, zip, ziphash)
826 if err != nil {
827 base.Fatalf("verifying %v", module.VersionError(mod, err))
828 }
829 return
830 }
831 h := string(data)
832 if !strings.HasPrefix(h, "h1:") {
833 base.Fatalf("verifying %v", module.VersionError(mod, fmt.Errorf("unexpected ziphash: %q", h)))
834 }
835
836 if err := checkModSum(f, mod, h); err != nil {
837 base.Fatalf("%s", err)
838 }
839 }
840
841
842 func goModSum(data []byte) (string, error) {
843 return dirhash.Hash1([]string{"go.mod"}, func(string) (io.ReadCloser, error) {
844 return io.NopCloser(bytes.NewReader(data)), nil
845 })
846 }
847
848
849
850 func checkGoMod(f *Fetcher, path, version string, data []byte) error {
851 h, err := goModSum(data)
852 if err != nil {
853 return &module.ModuleError{Path: path, Version: version, Err: fmt.Errorf("verifying go.mod: %v", err)}
854 }
855
856 return checkModSum(f, module.Version{Path: path, Version: version + "/go.mod"}, h)
857 }
858
859
860
861
862
863 func checkModSum(f *Fetcher, mod module.Version, h string) error {
864
865
866
867
868
869
870 f.mu.Lock()
871 inited, err := f.initGoSum()
872 if err != nil {
873 f.mu.Unlock()
874 return err
875 }
876 done := inited && haveModSumLocked(f, mod, h)
877 if inited {
878 st := f.sumState.status[modSum{mod, h}]
879 st.used = true
880 f.sumState.status[modSum{mod, h}] = st
881 }
882 f.mu.Unlock()
883
884 if done && mod.Path != "golang.org/toolchain" {
885 return nil
886 }
887
888
889 if useSumDB(mod) {
890
891 if err := checkSumDB(mod, h); err != nil {
892 return err
893 }
894 }
895
896 if done {
897 return nil
898 }
899
900
901 if inited {
902 f.mu.Lock()
903 addModSumLocked(f, mod, h)
904 st := f.sumState.status[modSum{mod, h}]
905 st.dirty = true
906 f.sumState.status[modSum{mod, h}] = st
907 f.mu.Unlock()
908 }
909 return nil
910 }
911
912
913
914
915 func haveModSumLocked(f *Fetcher, mod module.Version, h string) bool {
916 sumFileName := "go.sum"
917 if strings.HasSuffix(f.goSumFile, "go.work.sum") {
918 sumFileName = "go.work.sum"
919 }
920 for _, vh := range f.sumState.m[mod] {
921 if h == vh {
922 return true
923 }
924 if strings.HasPrefix(vh, "h1:") {
925 base.Fatalf("verifying %s@%s: checksum mismatch\n\tdownloaded: %v\n\t%s: %v"+goSumMismatch, mod.Path, mod.Version, h, sumFileName, vh)
926 }
927 }
928
929 foundMatch := false
930
931
932 for goSumFile, goSums := range f.sumState.w {
933 for _, vh := range goSums[mod] {
934 if h == vh {
935 foundMatch = true
936 } else if strings.HasPrefix(vh, "h1:") {
937 base.Fatalf("verifying %s@%s: checksum mismatch\n\tdownloaded: %v\n\t%s: %v"+goSumMismatch, mod.Path, mod.Version, h, goSumFile, vh)
938 }
939 }
940 }
941 return foundMatch
942 }
943
944
945
946 func addModSumLocked(f *Fetcher, mod module.Version, h string) {
947 if haveModSumLocked(f, mod, h) {
948 return
949 }
950 if len(f.sumState.m[mod]) > 0 {
951 fmt.Fprintf(os.Stderr, "warning: verifying %s@%s: unknown hashes in go.sum: %v; adding %v"+hashVersionMismatch, mod.Path, mod.Version, strings.Join(f.sumState.m[mod], ", "), h)
952 }
953 f.sumState.m[mod] = append(f.sumState.m[mod], h)
954 }
955
956
957
958 func checkSumDB(mod module.Version, h string) error {
959 modWithoutSuffix := mod
960 noun := "module"
961 if before, found := strings.CutSuffix(mod.Version, "/go.mod"); found {
962 noun = "go.mod"
963 modWithoutSuffix.Version = before
964 }
965
966 db, lines, err := lookupSumDB(mod)
967 if err != nil {
968 return module.VersionError(modWithoutSuffix, fmt.Errorf("verifying %s: %v", noun, err))
969 }
970
971 have := mod.Path + " " + mod.Version + " " + h
972 prefix := mod.Path + " " + mod.Version + " h1:"
973 for _, line := range lines {
974 if line == have {
975 return nil
976 }
977 if strings.HasPrefix(line, prefix) {
978 return module.VersionError(modWithoutSuffix, fmt.Errorf("verifying %s: checksum mismatch\n\tdownloaded: %v\n\t%s: %v"+sumdbMismatch, noun, h, db, line[len(prefix)-len("h1:"):]))
979 }
980 }
981 return module.VersionError(modWithoutSuffix, fmt.Errorf("verifying %s: checksum missing from sumdb response"+sumdbAbsent, noun))
982 }
983
984
985
986 func Sum(ctx context.Context, mod module.Version) string {
987 if cfg.GOMODCACHE == "" {
988
989 return ""
990 }
991
992 ziphash, err := CachePath(ctx, mod, "ziphash")
993 if err != nil {
994 return ""
995 }
996 data, err := lockedfile.Read(ziphash)
997 if err != nil {
998 return ""
999 }
1000 data = bytes.TrimSpace(data)
1001 if !isValidSum(data) {
1002 return ""
1003 }
1004 return string(data)
1005 }
1006
1007
1008
1009
1010
1011
1012 func isValidSum(data []byte) bool {
1013 if bytes.IndexByte(data, '\000') >= 0 {
1014 return false
1015 }
1016
1017 if len(data) != len("h1:")+base64.StdEncoding.EncodedLen(sha256.Size) {
1018 return false
1019 }
1020
1021 return true
1022 }
1023
1024 var ErrGoSumDirty = errors.New("updates to go.sum needed, disabled by -mod=readonly")
1025
1026
1027
1028
1029
1030
1031
1032 func (f *Fetcher) WriteGoSum(ctx context.Context, keep map[module.Version]bool, readonly bool) error {
1033 f.mu.Lock()
1034 defer f.mu.Unlock()
1035
1036
1037 if !f.sumState.enabled {
1038 return nil
1039 }
1040
1041
1042
1043
1044 dirty := false
1045 Outer:
1046 for m, hs := range f.sumState.m {
1047 for _, h := range hs {
1048 st := f.sumState.status[modSum{m, h}]
1049 if st.dirty && (!st.used || keep[m]) {
1050 dirty = true
1051 break Outer
1052 }
1053 }
1054 }
1055 if !dirty {
1056 return nil
1057 }
1058 if readonly {
1059 return ErrGoSumDirty
1060 }
1061 if fsys.Replaced(f.goSumFile) {
1062 base.Fatalf("go: updates to go.sum needed, but go.sum is part of the overlay specified with -overlay")
1063 }
1064
1065
1066
1067 if unlock, err := SideLock(ctx); err == nil {
1068 defer unlock()
1069 }
1070
1071 err := lockedfile.Transform(f.goSumFile, func(data []byte) ([]byte, error) {
1072 tidyGoSum := tidyGoSum(f, data, keep)
1073 return tidyGoSum, nil
1074 })
1075 if err != nil {
1076 return fmt.Errorf("updating go.sum: %w", err)
1077 }
1078
1079 f.sumState.status = make(map[modSum]modSumStatus)
1080 f.sumState.overwrite = false
1081 return nil
1082 }
1083
1084
1085
1086 func (f *Fetcher) TidyGoSum(keep map[module.Version]bool) (before, after []byte) {
1087 f.mu.Lock()
1088 defer f.mu.Unlock()
1089 before, err := lockedfile.Read(f.goSumFile)
1090 if err != nil && !errors.Is(err, fs.ErrNotExist) {
1091 base.Fatalf("reading go.sum: %v", err)
1092 }
1093 after = tidyGoSum(f, before, keep)
1094 return before, after
1095 }
1096
1097
1098
1099 func tidyGoSum(f *Fetcher, data []byte, keep map[module.Version]bool) []byte {
1100 if !f.sumState.overwrite {
1101
1102
1103
1104
1105 f.sumState.m = make(map[module.Version][]string, len(f.sumState.m))
1106 readGoSum(f.sumState.m, f.goSumFile, data)
1107 for ms, st := range f.sumState.status {
1108 if st.used && !sumInWorkspaceModulesLocked(f, ms.mod) {
1109 addModSumLocked(f, ms.mod, ms.sum)
1110 }
1111 }
1112 }
1113
1114 mods := make([]module.Version, 0, len(f.sumState.m))
1115 for m := range f.sumState.m {
1116 mods = append(mods, m)
1117 }
1118 module.Sort(mods)
1119
1120 var buf bytes.Buffer
1121 for _, m := range mods {
1122 list := f.sumState.m[m]
1123 sort.Strings(list)
1124 str.Uniq(&list)
1125 for _, h := range list {
1126 st := f.sumState.status[modSum{m, h}]
1127 if (!st.dirty || (st.used && keep[m])) && !sumInWorkspaceModulesLocked(f, m) {
1128 fmt.Fprintf(&buf, "%s %s %s\n", m.Path, m.Version, h)
1129 }
1130 }
1131 }
1132 return buf.Bytes()
1133 }
1134
1135 func sumInWorkspaceModulesLocked(f *Fetcher, m module.Version) bool {
1136 for _, goSums := range f.sumState.w {
1137 if _, ok := goSums[m]; ok {
1138 return true
1139 }
1140 }
1141 return false
1142 }
1143
1144
1145
1146
1147
1148
1149
1150 func (f *Fetcher) TrimGoSum(keep map[module.Version]bool) {
1151 f.mu.Lock()
1152 defer f.mu.Unlock()
1153 inited, err := f.initGoSum()
1154 if err != nil {
1155 base.Fatalf("%s", err)
1156 }
1157 if !inited {
1158 return
1159 }
1160
1161 for m, hs := range f.sumState.m {
1162 if !keep[m] {
1163 for _, h := range hs {
1164 f.sumState.status[modSum{m, h}] = modSumStatus{used: false, dirty: true}
1165 }
1166 f.sumState.overwrite = true
1167 }
1168 }
1169 }
1170
1171 const goSumMismatch = `
1172
1173 SECURITY ERROR
1174 This download does NOT match an earlier download recorded in go.sum.
1175 The bits may have been replaced on the origin server, or an attacker may
1176 have intercepted the download attempt.
1177
1178 For more information, see 'go help module-auth'.
1179 `
1180
1181 const sumdbMismatch = `
1182
1183 SECURITY ERROR
1184 This download does NOT match the one reported by the checksum server.
1185 The bits may have been replaced on the origin server, or an attacker may
1186 have intercepted the download attempt.
1187
1188 For more information, see 'go help module-auth'.
1189 `
1190
1191 const sumdbAbsent = `
1192
1193 SECURITY ERROR
1194 This download does NOT match one reported by the checksum server.
1195 The checksum server has provided checksums, but the checksums do
1196 not contain an entry for the download.
1197 The checksum server may be malfunctioning, or an attacker may have
1198 intercepted the checksum request.
1199 The download cannot be verified.
1200
1201 For more information, see 'go help module-auth'.
1202 `
1203
1204 const hashVersionMismatch = `
1205
1206 SECURITY WARNING
1207 This download is listed in go.sum, but using an unknown hash algorithm.
1208 The download cannot be verified.
1209
1210 For more information, see 'go help module-auth'.
1211
1212 `
1213
1214 var HelpModuleAuth = &base.Command{
1215 UsageLine: "module-auth",
1216 Short: "module authentication using go.sum",
1217 Long: `
1218 When the go command downloads a module zip file or go.mod file into the
1219 module cache, it computes a cryptographic hash and compares it with a known
1220 value to verify the file hasn't changed since it was first downloaded. Known
1221 hashes are stored in a file in the module root directory named go.sum. Hashes
1222 may also be downloaded from the checksum database depending on the values of
1223 GOSUMDB, GOPRIVATE, and GONOSUMDB.
1224
1225 For details, see https://go.dev/ref/mod#authenticating.
1226 `,
1227 }
1228
1229 var HelpPrivate = &base.Command{
1230 UsageLine: "private",
1231 Short: "configuration for downloading non-public code",
1232 Long: `
1233 The go command defaults to downloading modules from the public Go module
1234 mirror at proxy.golang.org. It also defaults to validating downloaded modules,
1235 regardless of source, against the public Go checksum database at sum.golang.org.
1236 These defaults work well for publicly available source code.
1237
1238 The GOPRIVATE environment variable controls which modules the go command
1239 considers to be private (not available publicly) and should therefore not use
1240 the proxy or checksum database. The variable is a comma-separated list of
1241 glob patterns (in the syntax of Go's path.Match) of module path prefixes.
1242 For example,
1243
1244 GOPRIVATE=*.corp.example.com,rsc.io/private
1245
1246 causes the go command to treat as private any module with a path prefix
1247 matching either pattern, including git.corp.example.com/xyzzy, rsc.io/private,
1248 and rsc.io/private/quux.
1249
1250 For fine-grained control over module download and validation, the GONOPROXY
1251 and GONOSUMDB environment variables accept the same kind of glob list
1252 and override GOPRIVATE for the specific decision of whether to use the proxy
1253 and checksum database, respectively.
1254
1255 For example, if a company ran a module proxy serving private modules,
1256 users would configure go using:
1257
1258 GOPRIVATE=*.corp.example.com
1259 GOPROXY=proxy.example.com
1260 GONOPROXY=none
1261
1262 The GOPRIVATE variable is also used to define the "public" and "private"
1263 patterns for the GOVCS variable; see 'go help vcs'. For that usage,
1264 GOPRIVATE applies even in GOPATH mode. In that case, it matches import paths
1265 instead of module paths.
1266
1267 The 'go env -w' command (see 'go help env') can be used to set these variables
1268 for future go command invocations.
1269
1270 For more details, see https://go.dev/ref/mod#private-modules.
1271 `,
1272 }
1273
View as plain text