crypto/internal/fips: add self-test mechanism

Updates #69536

Change-Id: Ib68b0e7058221a89908fd47f255f0a983883bee8
Reviewed-on: https://go-review.googlesource.com/c/go/+/621075
Reviewed-by: Daniel McCarney <daniel@binaryparadox.net>
Auto-Submit: Filippo Valsorda <filippo@golang.org>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Roland Shoemaker <roland@golang.org>
Reviewed-by: Carlos Amedee <carlos@golang.org>
This commit is contained in:
Filippo Valsorda 2024-10-18 18:54:45 +02:00 committed by Gopher Robot
parent ff86b8b62f
commit f505d6c581
8 changed files with 256 additions and 0 deletions

View File

@ -0,0 +1,53 @@
// 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
import (
"errors"
"internal/godebug"
"strings"
_ "unsafe" // for go:linkname
)
// fatal is [runtime.fatal], pushed via linkname.
//
//go:linkname fatal
func fatal(string)
// failfipscast is a GODEBUG key allowing simulation of a Cryptographic Algorithm
// Self-Test (CAST) failure, as required during FIPS 140-3 functional testing.
// The value is a substring of the target CAST name.
var failfipscast = godebug.New("#failfipscast")
// testingOnlyCASTHook is called during tests with each CAST name.
var testingOnlyCASTHook func(string)
// CAST runs the named Cryptographic Algorithm Self-Test (if compiled and
// operated in FIPS mode) and aborts the program (stopping the module
// input/output and entering the "error state") if the self-test fails.
//
// These are mandatory self-checks that must be performed by FIPS 140-3 modules
// before the algorithm is used. See Implementation Guidance 10.3.A.
//
// The name must not contain commas, colons, hashes, or equal signs.
//
// When calling this function, also add the calling package to cast_test.go.
func CAST(name string, f func() error) {
if strings.ContainsAny(name, ",#=:") {
panic("fips: invalid self-test name: " + name)
}
if testingOnlyCASTHook != nil {
testingOnlyCASTHook(name)
}
err := f()
if failfipscast.Value() != "" && strings.Contains(name, failfipscast.Value()) {
err = errors.New("simulated CAST failure")
}
if err != nil {
fatal("FIPS 140-3 self-test failed: " + name + ": " + err.Error())
panic("unreachable")
}
}

View File

@ -0,0 +1,51 @@
// 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_test
import (
"crypto/internal/fips"
"fmt"
"internal/testenv"
"strings"
"testing"
// Import packages that define CASTs to test them.
_ "crypto/internal/fips/hmac"
_ "crypto/internal/fips/sha256"
_ "crypto/internal/fips/sha3"
_ "crypto/internal/fips/sha512"
)
func TestCAST(t *testing.T) {
if len(fips.AllCASTs) == 0 {
t.Errorf("no CASTs to test")
}
for _, name := range fips.AllCASTs {
t.Logf("CAST %s completed successfully", name)
}
t.Run("SimulateFailures", func(t *testing.T) {
testenv.MustHaveExec(t)
for _, name := range fips.AllCASTs {
t.Run(name, func(t *testing.T) {
t.Parallel()
cmd := testenv.Command(t, testenv.Executable(t), "-test.run=TestCAST", "-test.v")
cmd = testenv.CleanCmdEnv(cmd)
cmd.Env = append(cmd.Env, fmt.Sprintf("GODEBUG=failfipscast=%s", name))
out, err := cmd.CombinedOutput()
if err == nil {
t.Error(err)
} else {
t.Logf("CAST %s failed and caused the program to exit", name)
t.Logf("%s", out)
}
if strings.Contains(string(out), "completed successfully") {
t.Errorf("CAST %s failure did not stop the program", name)
}
})
}
})
}

View File

@ -0,0 +1,13 @@
// 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
var AllCASTs []string
func init() {
testingOnlyCASTHook = func(name string) {
AllCASTs = append(AllCASTs, name)
}
}

View File

@ -0,0 +1,34 @@
// 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 hmac
import (
"bytes"
"crypto/internal/fips"
"crypto/internal/fips/sha256"
"errors"
)
func init() {
fips.CAST("HMAC-SHA2-256", func() error {
input := []byte{
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10,
}
want := []byte{
0xf0, 0x8d, 0x82, 0x8d, 0x4c, 0x9e, 0xad, 0x3d,
0xdc, 0x12, 0x9c, 0x4e, 0x70, 0xc4, 0x19, 0x2a,
0x4f, 0x12, 0x73, 0x23, 0x73, 0x77, 0x66, 0x05,
0x10, 0xee, 0x57, 0x6b, 0x3a, 0xc7, 0x14, 0x41,
}
h := New(sha256.New, input)
h.Write(input)
h.Write(input)
if got := h.Sum(nil); !bytes.Equal(got, want) {
return errors.New("unexpected result")
}
return nil
})
}

View File

@ -0,0 +1,32 @@
// 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 sha256
import (
"bytes"
"crypto/internal/fips"
"errors"
)
func init() {
fips.CAST("SHA2-256", func() error {
input := []byte{
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10,
}
want := []byte{
0x5d, 0xfb, 0xab, 0xee, 0xdf, 0x31, 0x8b, 0xf3,
0x3c, 0x09, 0x27, 0xc4, 0x3d, 0x76, 0x30, 0xf5,
0x1b, 0x82, 0xf3, 0x51, 0x74, 0x03, 0x01, 0x35,
0x4f, 0xa3, 0xd7, 0xfc, 0x51, 0xf0, 0x13, 0x2e,
}
h := New()
h.Write(input)
if got := h.Sum(nil); !bytes.Equal(got, want) {
return errors.New("unexpected result")
}
return nil
})
}

View File

@ -0,0 +1,32 @@
// 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 sha3
import (
"bytes"
"crypto/internal/fips"
"errors"
)
func init() {
fips.CAST("cSHAKE128", func() error {
input := []byte{
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10,
}
want := []byte{
0xd2, 0x17, 0x37, 0x39, 0xf6, 0xa1, 0xe4, 0x6e,
0x81, 0xe5, 0x70, 0xe3, 0x1b, 0x10, 0x4c, 0x82,
0xc5, 0x48, 0xee, 0xe6, 0x09, 0xf5, 0x89, 0x52,
0x52, 0xa4, 0x69, 0xd4, 0xd0, 0x76, 0x68, 0x6b,
}
h := NewCShake128(input, input)
h.Write(input)
if got := h.Sum(nil); !bytes.Equal(got, want) {
return errors.New("unexpected result")
}
return nil
})
}

View File

@ -0,0 +1,36 @@
// 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 sha512
import (
"bytes"
"crypto/internal/fips"
"errors"
)
func init() {
fips.CAST("SHA2-512", func() error {
input := []byte{
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10,
}
want := []byte{
0xb4, 0xc4, 0xe0, 0x46, 0x82, 0x6b, 0xd2, 0x61,
0x90, 0xd0, 0x97, 0x15, 0xfc, 0x31, 0xf4, 0xe6,
0xa7, 0x28, 0x20, 0x4e, 0xad, 0xd1, 0x12, 0x90,
0x5b, 0x08, 0xb1, 0x4b, 0x7f, 0x15, 0xc4, 0xf3,
0x8e, 0x29, 0xb2, 0xfc, 0x54, 0x26, 0x5a, 0x12,
0x63, 0x26, 0xc5, 0xbd, 0xea, 0x66, 0xc1, 0xb0,
0x8e, 0x9e, 0x47, 0x72, 0x3b, 0x2d, 0x70, 0x06,
0x5a, 0xc1, 0x26, 0x2e, 0xcc, 0x37, 0xbf, 0xb1,
}
h := New()
h.Write(input)
if got := h.Sum(nil); !bytes.Equal(got, want) {
return errors.New("unexpected result")
}
return nil
})
}

View File

@ -1038,6 +1038,11 @@ func rand_fatal(s string) {
fatal(s)
}
//go:linkname fips_fatal crypto/internal/fips.fatal
func fips_fatal(s string) {
fatal(s)
}
// throw triggers a fatal error that dumps a stack trace and exits.
//
// throw should be used for runtime-internal fatal errors where Go itself,