blob: 5bbc964b617b2b8f95fc25c5770acd599245ce4a [file] [log] [blame] [edit]
// 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 fipstest
import (
"crypto"
"crypto/rand"
"fmt"
"internal/testenv"
"io/fs"
"os"
"regexp"
"slices"
"strings"
"testing"
// Import packages that define CASTs to test them.
"crypto/internal/cryptotest"
_ "crypto/internal/fips140/aes"
_ "crypto/internal/fips140/aes/gcm"
_ "crypto/internal/fips140/drbg"
"crypto/internal/fips140/ecdh"
"crypto/internal/fips140/ecdsa"
"crypto/internal/fips140/ed25519"
_ "crypto/internal/fips140/hkdf"
_ "crypto/internal/fips140/hmac"
"crypto/internal/fips140/mlkem"
"crypto/internal/fips140/rsa"
"crypto/internal/fips140/sha256"
_ "crypto/internal/fips140/sha3"
_ "crypto/internal/fips140/sha512"
_ "crypto/internal/fips140/tls12"
_ "crypto/internal/fips140/tls13"
)
var allCASTs = []string{
"AES-CBC",
"CTR_DRBG",
"CounterKDF",
"DetECDSA P-256 SHA2-512 sign",
"ECDH PCT",
"ECDSA P-256 SHA2-512 sign and verify",
"ECDSA PCT",
"Ed25519 sign and verify",
"Ed25519 sign and verify PCT",
"HKDF-SHA2-256",
"HMAC-SHA2-256",
"KAS-ECC-SSC P-256",
"ML-KEM PCT", // -768
"ML-KEM PCT", // -1024
"ML-KEM-768",
"PBKDF2",
"RSA sign and verify PCT",
"RSASSA-PKCS-v1.5 2048-bit sign and verify",
"SHA2-256",
"SHA2-512",
"TLSv1.2-SHA2-256",
"TLSv1.3-SHA2-256",
"cSHAKE128",
}
func TestAllCASTs(t *testing.T) {
testenv.MustHaveSource(t)
// Ask "go list" for the location of the crypto/internal/fips140 tree, as it
// might be the unpacked frozen tree selected with GOFIPS140.
cmd := testenv.Command(t, testenv.GoToolPath(t), "list", "-f", `{{.Dir}}`, "crypto/internal/fips140")
out, err := cmd.CombinedOutput()
if err != nil {
t.Fatalf("go list: %v\n%s", err, out)
}
fipsDir := strings.TrimSpace(string(out))
t.Logf("FIPS module directory: %s", fipsDir)
// Find all invocations of fips140.CAST or fips140.PCT.
var foundCASTs []string
castRe := regexp.MustCompile(`fips140\.(CAST|PCT)\("([^"]+)"`)
if err := fs.WalkDir(os.DirFS(fipsDir), ".", func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
if d.IsDir() || !strings.HasSuffix(path, ".go") {
return nil
}
data, err := os.ReadFile(fipsDir + "/" + path)
if err != nil {
return err
}
for _, m := range castRe.FindAllSubmatch(data, -1) {
foundCASTs = append(foundCASTs, string(m[2]))
}
return nil
}); err != nil {
t.Fatalf("WalkDir: %v", err)
}
slices.Sort(foundCASTs)
if !slices.Equal(foundCASTs, allCASTs) {
t.Errorf("AllCASTs is out of date. Found CASTs: %#v", foundCASTs)
}
}
// TestConditionals causes the conditional CASTs and PCTs to be invoked.
func TestConditionals(t *testing.T) {
// ML-KEM PCT
kMLKEM, err := mlkem.GenerateKey768()
if err != nil {
t.Error(err)
} else {
// ML-KEM-768
kMLKEM.EncapsulationKey().Encapsulate()
}
// ECDH PCT
kDH, err := ecdh.GenerateKey(ecdh.P256(), rand.Reader)
if err != nil {
t.Error(err)
} else {
// KAS-ECC-SSC P-256
ecdh.ECDH(ecdh.P256(), kDH, kDH.PublicKey())
}
// ECDSA PCT
kDSA, err := ecdsa.GenerateKey(ecdsa.P256(), rand.Reader)
if err != nil {
t.Error(err)
} else {
// ECDSA P-256 SHA2-512 sign and verify
ecdsa.SignDeterministic(ecdsa.P256(), sha256.New, kDSA, make([]byte, 32))
}
// Ed25519 sign and verify PCT
k25519, err := ed25519.GenerateKey()
if err != nil {
t.Error(err)
} else {
// Ed25519 sign and verify
ed25519.Sign(k25519, make([]byte, 32))
}
// RSA sign and verify PCT
kRSA, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
t.Error(err)
} else {
// RSASSA-PKCS-v1.5 2048-bit sign and verify
rsa.SignPKCS1v15(kRSA, crypto.SHA256.String(), make([]byte, 32))
}
t.Log("completed successfully")
}
func TestCASTPasses(t *testing.T) {
moduleStatus(t)
testenv.MustHaveExec(t)
cryptotest.MustSupportFIPS140(t)
cmd := testenv.Command(t, testenv.Executable(t), "-test.run=^TestConditionals$", "-test.v")
cmd.Env = append(cmd.Env, "GODEBUG=fips140=debug")
out, err := cmd.CombinedOutput()
t.Logf("%s", out)
if err != nil || !strings.Contains(string(out), "completed successfully") {
t.Errorf("TestConditionals did not complete successfully")
}
for _, name := range allCASTs {
t.Run(name, func(t *testing.T) {
if !strings.Contains(string(out), fmt.Sprintf("passed: %s\n", name)) {
t.Errorf("CAST/PCT %s success was not logged", name)
} else {
t.Logf("CAST/PCT succeeded: %s", name)
}
})
}
}
func TestCASTFailures(t *testing.T) {
moduleStatus(t)
testenv.MustHaveExec(t)
cryptotest.MustSupportFIPS140(t)
for _, name := range allCASTs {
t.Run(name, func(t *testing.T) {
// Don't parallelize if running in verbose mode, to produce a less
// confusing recoding for the validation lab.
if !testing.Verbose() {
t.Parallel()
}
t.Logf("Testing CAST/PCT failure...")
cmd := testenv.Command(t, testenv.Executable(t), "-test.run=^TestConditionals$", "-test.v")
cmd.Env = append(cmd.Env, fmt.Sprintf("GODEBUG=failfipscast=%s,fips140=on", name))
out, err := cmd.CombinedOutput()
t.Logf("%s", out)
if err == nil {
t.Fatal("Test did not fail as expected")
}
if strings.Contains(string(out), "completed successfully") {
t.Errorf("CAST/PCT %s failure did not stop the program", name)
} else if !strings.Contains(string(out), "self-test failed: "+name) {
t.Errorf("CAST/PCT %s failure did not log the expected message", name)
} else {
t.Logf("CAST/PCT %s failed as expected and caused the program to exit", name)
}
})
}
}