GORISCV64, goRISCV64Changed = EnvOrAndChanged("GORISCV64", buildcfg.DefaultGORISCV64)
GOWASM, goWASMChanged = EnvOrAndChanged("GOWASM", fmt.Sprint(buildcfg.GOWASM))
+ GOFIPS140, GOFIPS140Changed = EnvOrAndChanged("GOFIPS140", buildcfg.GOFIPS140)
GOPROXY, GOPROXYChanged = EnvOrAndChanged("GOPROXY", "")
GOSUMDB, GOSUMDBChanged = EnvOrAndChanged("GOSUMDB", "")
GOPRIVATE = Getenv("GOPRIVATE")
// a different version (for example, when bisecting a regression).
{Name: "GOEXPERIMENT", Value: cfg.RawGOEXPERIMENT},
+ {Name: "GOFIPS140", Value: cfg.GOFIPS140, Changed: cfg.GOFIPS140Changed},
{Name: "GOFLAGS", Value: cfg.Getenv("GOFLAGS")},
{Name: "GOHOSTARCH", Value: runtime.GOARCH},
{Name: "GOHOSTOS", Value: runtime.GOOS},
--- /dev/null
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package fips implements support for the GOFIPS140 build setting.
+//
+// The GOFIPS140 build setting controls two aspects of the build:
+//
+// - Whether binaries are built to default to running in FIPS-140 mode,
+// meaning whether they default to GODEBUG=fips140=on or =off.
+//
+// - Which copy of the crypto/internal/fips source code to use.
+// The default is obviously GOROOT/src/crypto/internal/fips,
+// but earlier snapshots that have differing levels of external
+// validation and certification are stored in GOROOT/lib/fips140
+// and can be substituted into the build instead.
+//
+// This package provides the logic needed by the rest of the go command
+// to make those decisions and implement the resulting policy.
+//
+// [Init] must be called to initialize the FIPS logic. It may fail and
+// call base.Fatalf.
+//
+// When GOFIPS140=off, [Enabled] returns false, and the build is
+// unchanged from its usual behaviors.
+//
+// When GOFIPS140 is anything else, [Enabled] returns true, and the build
+// sets the default GODEBUG to include fips140=on. This will make
+// binaries change their behavior at runtime to confirm to various
+// FIPS-140 details. [cmd/go/internal/load.defaultGODEBUG] calls
+// [fips.Enabled] when preparing the default settings.
+//
+// For all builds, FIPS code and data is laid out in contiguous regions
+// that are conceptually concatenated into a "fips object file" that the
+// linker hashes and then binaries can re-hash at startup to detect
+// corruption of those symbols. When [Enabled] is true, the link step
+// passes -fipso={a.Objdir}/fips.o to the linker to save a copy of the
+// fips.o file. Since the first build target always uses a.Objdir set to
+// $WORK/b001, a build like
+//
+// GOFIPS140=latest go build -work my/binary
+//
+// will leave fips.o behind in $WORK/b001. Auditors like to be able to
+// see that file. Accordingly, when [Enabled] returns true,
+// [cmd/go/internal/work.Builder.useCache] arranges never to cache linker
+// output, so that the link step always runs, and fips.o is always left
+// behind in the link step. If this proves too slow, we could always
+// cache fips.o as an extra link output and then restore it when -work is
+// set, but we went a very long time never caching link steps at all, so
+// not caching them in FIPS mode seems perfectly fine.
+//
+// When GOFIPS140 is set to something besides off and latest, [Snapshot]
+// returns true, indicating that the build should replace the latest copy
+// of crypto/internal/fips with an earlier snapshot. The reason to do
+// this is to use a copy that has been through additional lab validation
+// (an "in-process" module) or NIST certification (a "certified" module).
+// This functionality is not yet implemented.
+package fips
+
+import (
+ "cmd/go/internal/base"
+ "cmd/go/internal/cfg"
+)
+
+// Init initializes the FIPS settings.
+// It must be called before using any other functions in this package.
+// If initialization fails, Init calls base.Fatalf.
+func Init() {
+ if initDone {
+ return
+ }
+ initDone = true
+ initVersion()
+}
+
+var initDone bool
+
+// checkInit panics if Init has not been called.
+func checkInit() {
+ if !initDone {
+ panic("fips: not initialized")
+ }
+}
+
+// Version reports the GOFIPS140 version in use,
+// which is either "off", "latest", or a version like "v1.2.3".
+// If GOFIPS140 is set to an alias like "inprocess" or "certified",
+// Version returns the underlying version.
+func Version() string {
+ checkInit()
+ return version
+}
+
+// Enabled reports whether FIPS mode is enabled at all.
+// That is, it reports whether GOFIPS140 is set to something besides "off".
+func Enabled() bool {
+ checkInit()
+ return version != "off"
+}
+
+// Snapshot reports whether FIPS mode is using a source snapshot
+// rather than $GOROOT/src/crypto/internal/fips.
+// That is, it reports whether GOFIPS140 is set to something besides "latest" or "off".
+func Snapshot() bool {
+ checkInit()
+ return version != "latest" && version != "off"
+}
+
+var version string
+
+func initVersion() {
+ // For off and latest, use the local source tree.
+ v := cfg.GOFIPS140
+ if v == "off" || v == "" {
+ version = "off"
+ return
+ }
+ if v == "latest" {
+ version = "latest"
+ return
+ }
+
+ base.Fatalf("go: unknown GOFIPS140 version %q", v)
+}
"strconv"
"strings"
+ "cmd/go/internal/fips"
"cmd/go/internal/gover"
"cmd/go/internal/modload"
)
}
var m map[string]string
+
+ // If GOFIPS140 is set to anything but "off",
+ // default to GODEBUG=fips140=on.
+ if fips.Enabled() {
+ if m == nil {
+ m = make(map[string]string)
+ }
+ m["fips140"] = "on"
+ }
+
+ // Add directives from main module go.mod.
for _, g := range modload.MainModules.Godebugs() {
if m == nil {
m = make(map[string]string)
}
m[g.Key] = g.Value
}
+
+ // Add directives from packages.
for _, list := range [][]build.Directive{p.Internal.Build.Directives, directives, testDirectives, xtestDirectives} {
for _, d := range list {
k, v, err := ParseGoDebug(d.Text)
"cmd/go/internal/base"
"cmd/go/internal/cfg"
+ "cmd/go/internal/fips"
"cmd/go/internal/fsys"
"cmd/go/internal/gover"
"cmd/go/internal/imports"
if cfg.RawGOEXPERIMENT != "" {
appendSetting("GOEXPERIMENT", cfg.RawGOEXPERIMENT)
}
+ if fips.Enabled() {
+ appendSetting("GOFIPS140", fips.Version())
+ }
appendSetting("GOOS", cfg.BuildContext.GOOS)
if key, val, _ := cfg.GetArchEnv(); key != "" && val != "" {
appendSetting(key, val)
"cmd/go/internal/base"
"cmd/go/internal/cfg"
+ "cmd/go/internal/fips"
"cmd/go/internal/fsys"
"cmd/go/internal/gover"
"cmd/go/internal/lockedfile"
// for example 'go mod tidy', that don't operate in workspace mode.
func InitWorkfile() {
// Initialize fsys early because we need overlay to read go.work file.
+ fips.Init()
if err := fsys.Init(); err != nil {
base.Fatal(err)
}
}
initialized = true
+ fips.Init()
+
// Keep in sync with WillBeEnabled. We perform extra validation here, and
// there are lots of diagnostics and side effects, so we can't use
// WillBeEnabled directly.
"cmd/go/internal/base"
"cmd/go/internal/cache"
"cmd/go/internal/cfg"
+ "cmd/go/internal/fips"
"cmd/go/internal/fsys"
"cmd/go/internal/str"
"cmd/internal/buildid"
a.buildID = actionID + buildIDSeparator + mainpkg.buildID + buildIDSeparator + contentID
}
+ // In FIPS mode, we disable any link caching,
+ // so that we always leave fips.o in $WORK/b001.
+ // This makes sure that labs validating the FIPS
+ // implementation can always run 'go build -work'
+ // and then find fips.o in $WORK/b001/fips.o.
+ // We could instead also save the fips.o and restore it
+ // to $WORK/b001 from the cache,
+ // but we went years without caching binaries anyway,
+ // so not caching them for FIPS will be fine, at least to start.
+ if a.Mode == "link" && fips.Enabled() && a.Package != nil && !strings.HasSuffix(a.Package.ImportPath, ".test") {
+ return false
+ }
+
// If user requested -a, we force a rebuild, so don't use the cache.
if cfg.BuildA {
if p := a.Package; p != nil && !p.Stale {
oldBuildID := a.buildID
a.buildID = id[1] + buildIDSeparator + id[2]
linkID := buildid.HashToString(b.linkActionID(a.triggers[0]))
- if id[0] == linkID {
+ if id[0] == linkID && !fips.Enabled() {
// Best effort attempt to display output from the compile and link steps.
// If it doesn't work, it doesn't work: reusing the cached binary is more
// important than reprinting diagnostic information.
"cmd/go/internal/base"
"cmd/go/internal/cfg"
+ "cmd/go/internal/fips"
"cmd/go/internal/fsys"
"cmd/go/internal/gover"
"cmd/go/internal/load"
if cfg.BuildBuildmode == "plugin" {
ldflags = append(ldflags, "-pluginpath", pluginPath(root))
}
+ if fips.Enabled() {
+ ldflags = append(ldflags, "-fipso", filepath.Join(root.Objdir, "fips.o"))
+ }
// Store BuildID inside toolchain binaries as a unique identifier of the
// tool being run, for use by content-based staleness determination.
--- /dev/null
+# list with GOFIPS140=off
+env GOFIPS140=off
+go list -f '{{.DefaultGODEBUG}}'
+! stdout fips140
+
+# list with GOFIPS140=latest
+env GOFIPS140=latest
+go list -f '{{.DefaultGODEBUG}}'
+stdout fips140=on
+
+[short] skip
+
+# build with GOFIPS140=off is cached
+env GOFIPS140=off
+go build -x -o x.exe
+! stderr .-fipso
+go build -x -o x.exe
+! stderr link
+
+# build with GOFIPS140=latest is NOT cached (need fipso)
+env GOFIPS140=latest
+go build -x -o x.exe
+stderr link.*-fipso
+go build -x -o x.exe
+stderr link.*-fipso
+
+# build test with GOFIPS140=off is cached
+env GOFIPS140=off
+go test -x -c
+! stderr .-fipso
+go test -x -c
+! stderr link
+
+# build test with GOFIPS140=latest is cached
+env GOFIPS140=latest
+go test -x -c
+stderr link.*-fipso
+go test -x -c
+! stderr link
+
+
+
+-- go.mod --
+module m
+-- x.go --
+package main
+import _ "crypto/sha256"
+func main() {
+}
+-- x_test.go --
+package main
+import "testing"
+func Test(t *testing.T) {}