mirror of https://github.com/golang/go.git
cmd/go: add basic GOFIPS140 support
GOFIPS140 does two things: (1) control whether to build binaries that run in FIPS-140 mode by default, and (2) control which version of the crypto/internal/fips source tree to use during a build. This CL implements part (1). It recognizes the GOFIPS140 settings "off" and "latest" and uses them to set the default GODEBUG=fips140 setting to "off" or "on" accordingly. The documentation for GOFIPS140 is in a follow-up CL. See cmd/go/internal/fips/fips.go for an overview. For #70200. Change-Id: I045f8ae0f19778a1e72a5cd2b6a7b0c88934fc30 Reviewed-on: https://go-review.googlesource.com/c/go/+/629198 Auto-Submit: Russ Cox <rsc@golang.org> Reviewed-by: Michael Matloob <matloob@golang.org> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
This commit is contained in:
parent
d13e6d0b08
commit
9935dd99da
|
|
@ -439,6 +439,7 @@ var (
|
|||
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")
|
||||
|
|
|
|||
|
|
@ -96,6 +96,7 @@ func MkEnv() []cfg.EnvVar {
|
|||
// 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},
|
||||
|
|
|
|||
|
|
@ -0,0 +1,124 @@
|
|||
// 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)
|
||||
}
|
||||
|
|
@ -14,6 +14,7 @@ import (
|
|||
"strconv"
|
||||
"strings"
|
||||
|
||||
"cmd/go/internal/fips"
|
||||
"cmd/go/internal/gover"
|
||||
"cmd/go/internal/modload"
|
||||
)
|
||||
|
|
@ -61,12 +62,25 @@ func defaultGODEBUG(p *Package, directives, testDirectives, xtestDirectives []bu
|
|||
}
|
||||
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ import (
|
|||
|
||||
"cmd/go/internal/base"
|
||||
"cmd/go/internal/cfg"
|
||||
"cmd/go/internal/fips"
|
||||
"cmd/go/internal/fsys"
|
||||
"cmd/go/internal/gover"
|
||||
"cmd/go/internal/imports"
|
||||
|
|
@ -2429,6 +2430,9 @@ func (p *Package) setBuildInfo(ctx context.Context, autoVCS bool) {
|
|||
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)
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import (
|
|||
|
||||
"cmd/go/internal/base"
|
||||
"cmd/go/internal/cfg"
|
||||
"cmd/go/internal/fips"
|
||||
"cmd/go/internal/fsys"
|
||||
"cmd/go/internal/gover"
|
||||
"cmd/go/internal/lockedfile"
|
||||
|
|
@ -355,6 +356,7 @@ func BinDir() string {
|
|||
// 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)
|
||||
}
|
||||
|
|
@ -414,6 +416,8 @@ func Init() {
|
|||
}
|
||||
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.
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import (
|
|||
"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"
|
||||
|
|
@ -447,6 +448,19 @@ func (b *Builder) useCache(a *Action, actionHash cache.ActionID, target string,
|
|||
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 {
|
||||
|
|
@ -506,7 +520,7 @@ func (b *Builder) useCache(a *Action, actionHash cache.ActionID, target string,
|
|||
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.
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import (
|
|||
|
||||
"cmd/go/internal/base"
|
||||
"cmd/go/internal/cfg"
|
||||
"cmd/go/internal/fips"
|
||||
"cmd/go/internal/fsys"
|
||||
"cmd/go/internal/gover"
|
||||
"cmd/go/internal/load"
|
||||
|
|
@ -614,6 +615,9 @@ func (gcToolchain) ld(b *Builder, root *Action, targetPath, importcfg, mainpkg s
|
|||
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.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,53 @@
|
|||
# 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) {}
|
||||
Loading…
Reference in New Issue