// 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) } }) } }