diff --git a/src/cmd/go/internal/fips140/fips140.go b/src/cmd/go/internal/fips140/fips140.go index 09e4141f99..64e8bded32 100644 --- a/src/cmd/go/internal/fips140/fips140.go +++ b/src/cmd/go/internal/fips140/fips140.go @@ -86,6 +86,9 @@ package fips140 import ( "context" + "crypto/sha256" + "fmt" + "io" "os" "path" "path/filepath" @@ -225,13 +228,66 @@ func initDir() { mod := module.Version{Path: "golang.org/fips140", Version: v} file := filepath.Join(cfg.GOROOT, "lib/fips140", v+".zip") - zdir, err := modfetch.NewFetcher().Unzip(context.Background(), mod, file) + ctx := context.Background() + + // The FIPS 140-3 Security Policy require checking the SHA-256 hash of the + // zip file. Verify it once against fips140.sum before unpacking it. + if _, err := modfetch.DownloadDir(ctx, mod); err != nil { + sumfile := filepath.Join(cfg.GOROOT, "lib/fips140/fips140.sum") + if err := verifyZipSum(file, sumfile); err != nil { + base.Fatalf("go: verifying GOFIPS140=%v: %v", v, err) + } + } + + zdir, err := modfetch.NewFetcher().Unzip(ctx, mod, file) if err != nil { base.Fatalf("go: unpacking GOFIPS140=%v: %v", v, err) } dir = filepath.Join(zdir, "fips140") } +// verifyZipSum checks that the SHA-256 hash of zipfile matches the entry +// for its base name in sumfile, which is expected to be in the format of +// GOROOT/lib/fips140/fips140.sum: "NAME SHA256HEX" lines, with "#" comments. +func verifyZipSum(zipfile, sumfile string) error { + sums, err := os.ReadFile(sumfile) + if err != nil { + return err + } + name := filepath.Base(zipfile) + var want string + for line := range strings.SplitSeq(string(sums), "\n") { + line = strings.TrimSpace(line) + if line == "" || strings.HasPrefix(line, "#") { + continue + } + n, h, ok := strings.Cut(line, " ") + if !ok { + continue + } + if n == name { + want = strings.TrimSpace(h) + break + } + } + if want == "" { + return fmt.Errorf("no SHA-256 hash for %s in %s", name, sumfile) + } + f, err := os.Open(zipfile) + if err != nil { + return err + } + defer f.Close() + h := sha256.New() + if _, err := io.Copy(h, f); err != nil { + return err + } + if got := fmt.Sprintf("%x", h.Sum(nil)); got != want { + return fmt.Errorf("SHA-256 hash of %s is %s, want %s (from %s)", name, got, want, sumfile) + } + return nil +} + // ResolveImport resolves the import path imp. // If it is of the form crypto/internal/fips140/foo // (not crypto/internal/fips140/v1.2.3/foo) diff --git a/src/cmd/go/internal/fips140/fips_test.go b/src/cmd/go/internal/fips140/fips_test.go index 53f0c9ab58..8f4a669eef 100644 --- a/src/cmd/go/internal/fips140/fips_test.go +++ b/src/cmd/go/internal/fips140/fips_test.go @@ -100,3 +100,43 @@ func TestSums(t *testing.T) { t.Errorf("GOROOT/lib/fips140/fips140.sum out of date. changes needed:\n%s", strings.Join(diff, "")) } } + +func TestVerifyZipSum(t *testing.T) { + dir := t.TempDir() + zipfile := filepath.Join(dir, "v1.2.3.zip") + data := []byte("not really a zip, but it does not matter here") + if err := os.WriteFile(zipfile, data, 0666); err != nil { + t.Fatal(err) + } + sum := sha256.Sum256(data) + + sumfile := filepath.Join(dir, "fips140.sum") + write := func(contents string) { + if err := os.WriteFile(sumfile, []byte(contents), 0666); err != nil { + t.Fatal(err) + } + } + + // Matching hash with comments and a second entry passes. + write(fmt.Sprintf("# comment\n\nv1.2.3.zip %x\nother.zip 0000000000000000000000000000000000000000000000000000000000000000\n", sum[:])) + if err := verifyZipSum(zipfile, sumfile); err != nil { + t.Errorf("unexpected error: %v", err) + } + + // Missing entry for this zip fails. + write("# only comments\n") + if err := verifyZipSum(zipfile, sumfile); err == nil { + t.Errorf("expected error when hash entry is missing") + } + + // Wrong hash fails. + write("v1.2.3.zip 0000000000000000000000000000000000000000000000000000000000000000\n") + if err := verifyZipSum(zipfile, sumfile); err == nil { + t.Errorf("expected error when hash does not match") + } + + // Missing sum file fails. + if err := verifyZipSum(zipfile, filepath.Join(dir, "does-not-exist.sum")); err == nil { + t.Errorf("expected error when sum file is missing") + } +}