]> Cypherpunks repositories - gostls13.git/commitdiff
crypto/internal/fips: move most tests to crypto/internal/fipstest
authorFilippo Valsorda <filippo@golang.org>
Thu, 14 Nov 2024 11:41:47 +0000 (12:41 +0100)
committerGopher Robot <gobot@golang.org>
Tue, 19 Nov 2024 22:29:12 +0000 (22:29 +0000)
As explained in fips_test.go, we generally want to minimize tests inside
the FIPS module. When there is a relevant calling package, the tests
should go there, otherwise in fipstest.

This required redoing a bit the CAST failure tests, but the new version
is actually more robust because it will fail if a _ import is missing.

Since TestCAST doesn't print a line for each passed CAST anymore, made
GODEBUG=fips140=debug do that, in case we need to show it to the lab.

For #69536

Change-Id: I0c1b82a4e9ee39e8df9bbe95bebb0527753f51c8
Reviewed-on: https://go-review.googlesource.com/c/go/+/627955
Reviewed-by: Daniel McCarney <daniel@binaryparadox.net>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
Auto-Submit: Filippo Valsorda <filippo@golang.org>
Reviewed-by: Russ Cox <rsc@golang.org>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>

26 files changed:
src/crypto/internal/fips/cast.go
src/crypto/internal/fips/cast_external_test.go [deleted file]
src/crypto/internal/fips/cast_test.go [deleted file]
src/crypto/internal/fips/check/check.go
src/crypto/internal/fips/check/export_test.go [deleted file]
src/crypto/internal/fips/drbg/ctrdrbg_test.go [deleted file]
src/crypto/internal/fips/fips.go
src/crypto/internal/fips/mlkem/cast.go
src/crypto/internal/fips/mlkem/generate1024.go
src/crypto/internal/fips/mlkem/mlkem1024.go
src/crypto/internal/fips/mlkem/mlkem768.go
src/crypto/internal/fipstest/acvp_capabilities.json [moved from src/crypto/internal/fips/acvp_capabilities.json with 100% similarity]
src/crypto/internal/fipstest/acvp_test.config.json [moved from src/crypto/internal/fips/acvp_test.config.json with 100% similarity]
src/crypto/internal/fipstest/acvp_test.go [moved from src/crypto/internal/fips/acvp_test.go with 99% similarity]
src/crypto/internal/fipstest/alias_test.go [moved from src/crypto/internal/fips/alias/alias_test.go with 89% similarity]
src/crypto/internal/fipstest/cast_test.go [new file with mode: 0644]
src/crypto/internal/fipstest/check_test.go [moved from src/crypto/internal/fips/check/check_test.go with 94% similarity]
src/crypto/internal/fipstest/cmac_test.go [moved from src/crypto/internal/fips/aes/gcm/cmac_test.go with 82% similarity]
src/crypto/internal/fipstest/ctrdrbg_test.go [new file with mode: 0644]
src/crypto/internal/fipstest/fips_test.go [new file with mode: 0644]
src/crypto/internal/fipstest/hkdf_test.go [moved from src/crypto/internal/fips/hkdf/hkdf_test.go with 99% similarity]
src/crypto/internal/fipstest/indicator_test.go [moved from src/crypto/internal/fips/indicator_test.go with 99% similarity]
src/crypto/internal/fipstest/mlkem_test.go [moved from src/crypto/internal/fips/mlkem/mlkem_test.go with 95% similarity]
src/crypto/internal/fipstest/sha3_test.go [moved from src/crypto/internal/fips/sha3/sha3_test.go with 98% similarity]
src/crypto/internal/fipstest/sshkdf_test.go [moved from src/crypto/internal/fips/ssh/kdf_test.go with 98% similarity]
src/crypto/internal/fipstest/xaes_test.go [moved from src/crypto/internal/fips/aes/gcm/ctrkdf_test.go with 97% similarity]

index 1afbfcd2450884008e1212551af9363d50991e46..d63d39b4b6932a3312db52584da0453a4a44f068 100644 (file)
@@ -21,15 +21,12 @@ func fatal(string)
 // 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 or Pairwise Consistency
 // Test (if operated in FIPS mode) and aborts the program (stopping the module
 // input/output and entering the "error state") if the self-test fails.
 //
 // CASTs are mandatory self-checks that must be performed by FIPS 140-3 modules
-// before the algorithm is used. See Implementation Guidance 10.3.A. PCTs  are
+// before the algorithm is used. See Implementation Guidance 10.3.A. PCTs are
 // mandatory for every key pair that is generated/imported, including ephemeral
 // keys (which effectively doubles the cost of key establishment). See
 // Implementation Guidance 10.3.A Additional Comment 1.
@@ -37,15 +34,12 @@ var testingOnlyCASTHook func(string)
 // The name must not contain commas, colons, hashes, or equal signs.
 //
 // When calling this function from init(), also import the calling package from
-// cast_external_test.go, while if calling it from key generation/importing, add
-// an invocation to TestCAST.
+// crypto/internal/fipstest, while if calling it from key generation/importing, add
+// an invocation to fipstest.TestPCTs.
 func CAST(name string, f func() error) {
        if strings.ContainsAny(name, ",#=:") {
                panic("fips: invalid self-test name: " + name)
        }
-       if testingOnlyCASTHook != nil {
-               testingOnlyCASTHook(name)
-       }
        if !Enabled {
                return
        }
@@ -58,4 +52,7 @@ func CAST(name string, f func() error) {
                fatal("FIPS 140-3 self-test failed: " + name + ": " + err.Error())
                panic("unreachable")
        }
+       if debug {
+               println("FIPS 140-3 self-test passed:", name)
+       }
 }
diff --git a/src/crypto/internal/fips/cast_external_test.go b/src/crypto/internal/fips/cast_external_test.go
deleted file mode 100644 (file)
index 0b89565..0000000
+++ /dev/null
@@ -1,63 +0,0 @@
-// 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/aes"
-       _ "crypto/internal/fips/aes/gcm"
-       _ "crypto/internal/fips/drbg"
-       _ "crypto/internal/fips/hkdf"
-       _ "crypto/internal/fips/hmac"
-       "crypto/internal/fips/mlkem"
-       _ "crypto/internal/fips/sha256"
-       _ "crypto/internal/fips/sha3"
-       _ "crypto/internal/fips/sha512"
-       _ "crypto/internal/fips/tls12"
-       _ "crypto/internal/fips/tls13"
-)
-
-func TestCAST(t *testing.T) {
-       if len(fips.AllCASTs) == 0 {
-               t.Errorf("no CASTs to test")
-       }
-
-       // Cause PCTs to be invoked.
-       mlkem.GenerateKey768()
-
-       if fips.Enabled {
-               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,fips140=on", 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)
-                               }
-                       })
-               }
-       })
-}
diff --git a/src/crypto/internal/fips/cast_test.go b/src/crypto/internal/fips/cast_test.go
deleted file mode 100644 (file)
index 9a20638..0000000
+++ /dev/null
@@ -1,13 +0,0 @@
-// 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)
-       }
-}
index 86801c433292713ab63dd1fda5fc064376841936..5303650d2c427bacf8272783d73dd660b1e77bdd 100644 (file)
@@ -31,10 +31,10 @@ func Enabled() bool {
 }
 
 var enabled bool  // set when verification is enabled
-var verified bool // set when verification succeeds, for testing
+var Verified bool // set when verification succeeds, for testing
 
-// supported reports whether the current GOOS/GOARCH is supported at all.
-func supported() bool {
+// Supported reports whether the current GOOS/GOARCH is Supported at all.
+func Supported() bool {
        // See cmd/internal/obj/fips.go's EnableFIPS for commentary.
        switch {
        case runtime.GOARCH == "wasm",
@@ -46,11 +46,11 @@ func supported() bool {
        return true
 }
 
-// linkinfo holds the go:fipsinfo symbol prepared by the linker.
+// Linkinfo holds the go:fipsinfo symbol prepared by the linker.
 // See cmd/link/internal/ld/fips.go for details.
 //
-//go:linkname linkinfo go:fipsinfo
-var linkinfo struct {
+//go:linkname Linkinfo go:fipsinfo
+var Linkinfo struct {
        Magic [16]byte
        Sum   [32]byte
        Self  uintptr
@@ -95,11 +95,11 @@ func init() {
                panic("fips140: unknown GODEBUG setting fips140=" + v)
        }
 
-       if !supported() {
+       if !Supported() {
                panic("fips140: unavailable on " + runtime.GOOS + "-" + runtime.GOARCH)
        }
 
-       if linkinfo.Magic[0] != 0xff || string(linkinfo.Magic[1:]) != fipsMagic || linkinfo.Sum == zeroSum {
+       if Linkinfo.Magic[0] != 0xff || string(Linkinfo.Magic[1:]) != fipsMagic || Linkinfo.Sum == zeroSum {
                panic("fips140: no verification checksum found")
        }
 
@@ -120,7 +120,7 @@ func init() {
        w.Write([]byte("go fips object v1\n"))
 
        var nbuf [8]byte
-       for _, sect := range linkinfo.Sects {
+       for _, sect := range Linkinfo.Sects {
                n := uintptr(sect.End) - uintptr(sect.Start)
                byteorder.BePutUint64(nbuf[:], uint64(n))
                w.Write(nbuf[:])
@@ -128,7 +128,7 @@ func init() {
        }
        sum := h.Sum(nil)
 
-       if [32]byte(sum) != linkinfo.Sum {
+       if [32]byte(sum) != Linkinfo.Sum {
                panic("fips140: verification mismatch")
        }
 
@@ -136,5 +136,5 @@ func init() {
                println("fips140: verified code+data")
        }
 
-       verified = true
+       Verified = true
 }
diff --git a/src/crypto/internal/fips/check/export_test.go b/src/crypto/internal/fips/check/export_test.go
deleted file mode 100644 (file)
index e713970..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-// 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 check
-
-var Verified = &verified
-var Linkinfo = &linkinfo
-var Supported = supported
diff --git a/src/crypto/internal/fips/drbg/ctrdrbg_test.go b/src/crypto/internal/fips/drbg/ctrdrbg_test.go
deleted file mode 100644 (file)
index ca124cc..0000000
+++ /dev/null
@@ -1,49 +0,0 @@
-// 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 drbg_test
-
-import (
-       "bytes"
-       "crypto/internal/fips/drbg"
-       "crypto/internal/fips/subtle"
-       "encoding/hex"
-       "testing"
-)
-
-func TestCounter(t *testing.T) {
-       // https://github.com/usnistgov/ACVP-Server/blob/fb44dce/gen-val/json-files/ctrDRBG-1.0/prompt.json#L4447-L4482
-
-       entropyInput := decodeHex("9FCBB4CCC0135C484BDED061DA9FD70748682FE84166B97FF53F9AA1909B2E95D3D529C0F453B3AC575D12AA441CC5CD")
-       persoString := decodeHex("2C9FED0B39556CDBE699EBCA2A0EC7EECB287E8744475050C572FA8AE9ED0A4A7D6F1CABF1C4278532FB20AF7D64BD32")
-       reseedEntropy := decodeHex("913C0DA19B010EDDD55A7A4F3F713EEF5B1534D34360A7EC376AE71A6B340043CC7726F762CB853453F399B3A645062A")
-       reseedAdditional := decodeHex("2D9D4EC141A22E6CD2F6EE4F6719CF6BDF95CFE50B8D5EA6C87D38B4B872706FFF80B0380BB90E9C42D11D6526E56C29")
-       additional1 := decodeHex("A642F06D327828F3E84564A3E37D60C157073B95864CA07981B0189668A0D978CD5DC68F06801CEFF0DC839A312B028E")
-       additional2 := decodeHex("9DB14BABFA9107C88BA92073C0B4A65E89147EA06D74B894142979482F452915B35B5636F9B8A951759735ADE7C8D5D1")
-       returnedBits := decodeHex("F10C645683FF0131254052ED4C698122B46B563654C29D728AC191CA4AAEFE649EEFE4C6FC33B25BB739294DD5CF578099F856C98D98000CBF971F1E6EA900822FF8C110118F6520471744D3F8A3F5C7D568494240E57F5488AF9C9F9F4E7322F56CCD843C0DBFCE9170C02E205389420527F23EDB3369D9FCC5E34901B5BA4EB71B973FC7982FFE0899FF7FE53EE0C4F51A3EF93EF9C6D4D279DD7536F8776BE94AAA05E89EF6E6AEE8832B4B42FFCA5FB91EC0273F9EF945865512889B0C5EE141D1B38DF827D2A694835561628C6F9B093A01A835F07ADBB9E03FEBF93389E8F3B86E1E0ABF1F9958FA286AD995289C2F606D1A9043A166C1AFE8D00769C712650819C9068A4BD22717C98338395A7BA6E95B5178BFBF4EFB0F05A91713BA8BF2127A6BA1EDFA6D1CAB05C03EE0D2AFE1DA4EB8F2C579EC872FF4B602027EF4BDCF2F4B01423F8E600A13D7CACB6AB83263BA58F907694AF614A6724FD0E4C627A0D91DDC6716C697FACE6F4808A4F37B731DE4E0CD4766CEADAAAF47992505299C72AC1A6E9A8335B8D7E501B3841188D0DA4DE5267674444DC2B0CF9F010756FA865A25CA3F1B24C34E845B2259926B6A867A7684DE68A6137C4FB0F47A2E54AE9E6455BEBA0B0A9629644FE9E378EE95386443BA977124FFD1192E9F460684C7B09FA99F5F93F04F56FD7955E042187887CE696F1934017E458B16B5C9")
-
-       // We don't support personalization strings, but the pre-generated JSON
-       // vectors always use them, so just pre-mix them.
-       var seed [drbg.SeedSize]byte
-       subtle.XORBytes(seed[:], entropyInput, persoString)
-       c := drbg.NewCounter(&seed)
-
-       c.Reseed((*[48]byte)(reseedEntropy), (*[48]byte)(reseedAdditional))
-
-       buf := make([]byte, len(returnedBits))
-       c.Generate(buf, (*[48]byte)(additional1))
-
-       c.Generate(buf, (*[48]byte)(additional2))
-       if !bytes.Equal(buf, returnedBits) {
-               t.Errorf("unexpected output:\n%x\n%x", buf, returnedBits)
-       }
-}
-
-func decodeHex(s string) []byte {
-       b, err := hex.DecodeString(s)
-       if err != nil {
-               panic(err)
-       }
-       return b
-}
index e7628beac20c0ecdcc1d0954aa3bdc7033660792..a6418a4eb1aadc610c8a3ba9e3b9011a75a47b54 100644 (file)
@@ -8,9 +8,14 @@ import "internal/godebug"
 
 var Enabled bool
 
+var debug bool
+
 func init() {
        switch godebug.New("#fips140").Value() {
-       case "on", "debug", "only":
+       case "on", "only":
+               Enabled = true
+       case "debug":
                Enabled = true
+               debug = true
        }
 }
index f712fbb110fbf3aad5bffafa60d4b0791f485c7b..de4988527ec9095b3a491403aef82fafa5bfad85 100644 (file)
@@ -39,7 +39,7 @@ func init() {
                dk := &DecapsulationKey768{}
                kemKeyGen(dk, d, z)
                ek := dk.EncapsulationKey()
-               c, Ke := kemEncaps(nil, ek, m)
+               c, Ke := ek.EncapsulateInternal(m)
                Kd, err := dk.Decapsulate(c)
                if err != nil {
                        return err
index 94f22d54f995429f93b1367e1736ea32e6ab1884..e002bf1414ae966b8e8506db2cfffcb760a40cb9 100644 (file)
@@ -40,8 +40,9 @@ var replacements = map[string]string{
        "kemDecaps":  "kemDecaps1024",
        "pkeDecrypt": "pkeDecrypt1024",
 
-       "GenerateKey768": "GenerateKey1024",
-       "generateKey":    "generateKey1024",
+       "GenerateKey768":         "GenerateKey1024",
+       "GenerateKeyInternal768": "GenerateKeyInternal1024",
+       "generateKey":            "generateKey1024",
 
        "kemKeyGen": "kemKeyGen1024",
        "kemPCT":    "kemPCT1024",
index 6167c8bb8fe864c39bcaa66dc2c7782cc8611048..5ab94f99e51272b055d7dfd2592250f443d46354 100644 (file)
@@ -92,9 +92,18 @@ func generateKey1024(dk *DecapsulationKey1024) (*DecapsulationKey1024, error) {
        drbg.Read(z[:])
        kemKeyGen1024(dk, &d, &z)
        fips.CAST("ML-KEM PCT", func() error { return kemPCT1024(dk) })
+       fips.RecordApproved()
        return dk, nil
 }
 
+// GenerateKeyInternal1024 is a derandomized version of GenerateKey1024,
+// exclusively for use in tests.
+func GenerateKeyInternal1024(d, z *[32]byte) *DecapsulationKey1024 {
+       dk := &DecapsulationKey1024{}
+       kemKeyGen1024(dk, d, z)
+       return dk
+}
+
 // NewDecapsulationKey1024 parses a decapsulation key from a 64-byte
 // seed in the "d || z" form. The seed must be uniformly random.
 func NewDecapsulationKey1024(seed []byte) (*DecapsulationKey1024, error) {
@@ -111,6 +120,7 @@ func newKeyFromSeed1024(dk *DecapsulationKey1024, seed []byte) (*DecapsulationKe
        z := (*[32]byte)(seed[32:])
        kemKeyGen1024(dk, d, z)
        fips.CAST("ML-KEM PCT", func() error { return kemPCT1024(dk) })
+       fips.RecordApproved()
        return dk, nil
 }
 
@@ -120,8 +130,6 @@ func newKeyFromSeed1024(dk *DecapsulationKey1024, seed []byte) (*DecapsulationKe
 // K-PKE.KeyGen according to FIPS 203, Algorithm 13. The two are merged to save
 // copies and allocations.
 func kemKeyGen1024(dk *DecapsulationKey1024, d, z *[32]byte) {
-       fips.RecordApproved()
-
        dk.d = *d
        dk.z = *z
 
@@ -201,18 +209,21 @@ func (ek *EncapsulationKey1024) encapsulate(cc *[CiphertextSize1024]byte) (ciphe
        drbg.Read(m[:])
        // Note that the modulus check (step 2 of the encapsulation key check from
        // FIPS 203, Section 7.2) is performed by polyByteDecode in parseEK1024.
+       fips.RecordApproved()
        return kemEncaps1024(cc, ek, &m)
 }
 
+// EncapsulateInternal is a derandomized version of Encapsulate, exclusively for
+// use in tests.
+func (ek *EncapsulationKey1024) EncapsulateInternal(m *[32]byte) (ciphertext, sharedKey []byte) {
+       cc := &[CiphertextSize1024]byte{}
+       return kemEncaps1024(cc, ek, m)
+}
+
 // kemEncaps1024 generates a shared key and an associated ciphertext.
 //
 // It implements ML-KEM.Encaps_internal according to FIPS 203, Algorithm 17.
 func kemEncaps1024(cc *[CiphertextSize1024]byte, ek *EncapsulationKey1024, m *[messageSize]byte) (c, K []byte) {
-       fips.RecordApproved()
-       if cc == nil {
-               cc = &[CiphertextSize1024]byte{}
-       }
-
        g := sha3.New512()
        g.Write(m[:])
        g.Write(ek.h[:])
index 6c5cb85535ccb34ccf95fd2fc75abf8ced178e99..df49f51b8f237aedf25f89913eedddd3d054ba69 100644 (file)
@@ -149,9 +149,18 @@ func generateKey(dk *DecapsulationKey768) (*DecapsulationKey768, error) {
        drbg.Read(z[:])
        kemKeyGen(dk, &d, &z)
        fips.CAST("ML-KEM PCT", func() error { return kemPCT(dk) })
+       fips.RecordApproved()
        return dk, nil
 }
 
+// GenerateKeyInternal768 is a derandomized version of GenerateKey768,
+// exclusively for use in tests.
+func GenerateKeyInternal768(d, z *[32]byte) *DecapsulationKey768 {
+       dk := &DecapsulationKey768{}
+       kemKeyGen(dk, d, z)
+       return dk
+}
+
 // NewDecapsulationKey768 parses a decapsulation key from a 64-byte
 // seed in the "d || z" form. The seed must be uniformly random.
 func NewDecapsulationKey768(seed []byte) (*DecapsulationKey768, error) {
@@ -168,6 +177,7 @@ func newKeyFromSeed(dk *DecapsulationKey768, seed []byte) (*DecapsulationKey768,
        z := (*[32]byte)(seed[32:])
        kemKeyGen(dk, d, z)
        fips.CAST("ML-KEM PCT", func() error { return kemPCT(dk) })
+       fips.RecordApproved()
        return dk, nil
 }
 
@@ -177,8 +187,6 @@ func newKeyFromSeed(dk *DecapsulationKey768, seed []byte) (*DecapsulationKey768,
 // K-PKE.KeyGen according to FIPS 203, Algorithm 13. The two are merged to save
 // copies and allocations.
 func kemKeyGen(dk *DecapsulationKey768, d, z *[32]byte) {
-       fips.RecordApproved()
-
        dk.d = *d
        dk.z = *z
 
@@ -258,18 +266,21 @@ func (ek *EncapsulationKey768) encapsulate(cc *[CiphertextSize768]byte) (ciphert
        drbg.Read(m[:])
        // Note that the modulus check (step 2 of the encapsulation key check from
        // FIPS 203, Section 7.2) is performed by polyByteDecode in parseEK.
+       fips.RecordApproved()
        return kemEncaps(cc, ek, &m)
 }
 
+// EncapsulateInternal is a derandomized version of Encapsulate, exclusively for
+// use in tests.
+func (ek *EncapsulationKey768) EncapsulateInternal(m *[32]byte) (ciphertext, sharedKey []byte) {
+       cc := &[CiphertextSize768]byte{}
+       return kemEncaps(cc, ek, m)
+}
+
 // kemEncaps generates a shared key and an associated ciphertext.
 //
 // It implements ML-KEM.Encaps_internal according to FIPS 203, Algorithm 17.
 func kemEncaps(cc *[CiphertextSize768]byte, ek *EncapsulationKey768, m *[messageSize]byte) (c, K []byte) {
-       fips.RecordApproved()
-       if cc == nil {
-               cc = &[CiphertextSize768]byte{}
-       }
-
        g := sha3.New512()
        g.Write(m[:])
        g.Write(ek.h[:])
similarity index 99%
rename from src/crypto/internal/fips/acvp_test.go
rename to src/crypto/internal/fipstest/acvp_test.go
index 667685baef944dbd11c4a774cb713ffa37bc2f7a..e0748100c9637143136c73e7ae3da3d5cb750ddc 100644 (file)
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
+package fipstest
+
 // A module wrapper adapting the Go FIPS module to the protocol used by the
 // BoringSSL project's `acvptool`.
 //
@@ -15,7 +17,6 @@
 // and module wrappers.
 //
 // [0]:https://boringssl.googlesource.com/boringssl/+/refs/heads/master/util/fipstools/acvp/ACVP.md#testing-other-fips-modules
-package fips_test
 
 import (
        "bufio"
similarity index 89%
rename from src/crypto/internal/fips/alias/alias_test.go
rename to src/crypto/internal/fipstest/alias_test.go
index a68fb33667bf633067dbef222387f230ca51a918..e3cadaa20aac5d43880c4681755c12e360c7621d 100644 (file)
@@ -2,9 +2,12 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-package alias
+package fipstest
 
-import "testing"
+import (
+       "crypto/internal/fips/alias"
+       "testing"
+)
 
 var a, b [100]byte
 
@@ -28,11 +31,11 @@ var aliasingTests = []struct {
 }
 
 func testAliasing(t *testing.T, i int, x, y []byte, anyOverlap, inexactOverlap bool) {
-       any := AnyOverlap(x, y)
+       any := alias.AnyOverlap(x, y)
        if any != anyOverlap {
                t.Errorf("%d: wrong AnyOverlap result, expected %v, got %v", i, anyOverlap, any)
        }
-       inexact := InexactOverlap(x, y)
+       inexact := alias.InexactOverlap(x, y)
        if inexact != inexactOverlap {
                t.Errorf("%d: wrong InexactOverlap result, expected %v, got %v", i, inexactOverlap, any)
        }
diff --git a/src/crypto/internal/fipstest/cast_test.go b/src/crypto/internal/fipstest/cast_test.go
new file mode 100644 (file)
index 0000000..9d6483b
--- /dev/null
@@ -0,0 +1,100 @@
+// 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 (
+       "fmt"
+       "internal/testenv"
+       "io/fs"
+       "os"
+       "regexp"
+       "strings"
+       "testing"
+
+       // Import packages that define CASTs to test them.
+       _ "crypto/internal/fips/aes"
+       _ "crypto/internal/fips/aes/gcm"
+       _ "crypto/internal/fips/drbg"
+       _ "crypto/internal/fips/hkdf"
+       _ "crypto/internal/fips/hmac"
+       "crypto/internal/fips/mlkem"
+       _ "crypto/internal/fips/sha256"
+       _ "crypto/internal/fips/sha3"
+       _ "crypto/internal/fips/sha512"
+       _ "crypto/internal/fips/tls12"
+       _ "crypto/internal/fips/tls13"
+)
+
+func findAllCASTs(t *testing.T) map[string]struct{} {
+       testenv.MustHaveSource(t)
+
+       // Ask "go list" for the location of the crypto/internal/fips tree, as it
+       // might be the unpacked frozen tree selected with GOFIPS140.
+       cmd := testenv.Command(t, testenv.GoToolPath(t), "list", "-f", `{{.Dir}}`, "crypto/internal/fips")
+       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 fips.CAST.
+       allCASTs := make(map[string]struct{})
+       castRe := regexp.MustCompile(`fips\.CAST\("([^"]+)"`)
+       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) {
+                       allCASTs[string(m[1])] = struct{}{}
+               }
+               return nil
+       }); err != nil {
+               t.Fatalf("WalkDir: %v", err)
+       }
+
+       return allCASTs
+}
+
+// TestPCTs causes the conditional PCTs to be invoked.
+func TestPCTs(t *testing.T) {
+       mlkem.GenerateKey768()
+       t.Log("completed successfully")
+}
+
+func TestCASTFailures(t *testing.T) {
+       testenv.MustHaveExec(t)
+
+       allCASTs := findAllCASTs(t)
+       if len(allCASTs) == 0 {
+               t.Fatal("no CASTs found")
+       }
+
+       for name := range allCASTs {
+               t.Run(name, func(t *testing.T) {
+                       t.Parallel()
+                       cmd := testenv.Command(t, testenv.Executable(t), "-test.run=TestPCTs", "-test.v")
+                       cmd = testenv.CleanCmdEnv(cmd)
+                       cmd.Env = append(cmd.Env, fmt.Sprintf("GODEBUG=failfipscast=%s,fips140=on", 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)
+                       }
+               })
+       }
+}
similarity index 94%
rename from src/crypto/internal/fips/check/check_test.go
rename to src/crypto/internal/fipstest/check_test.go
index f0ca7f42511f5d80fbb7850726fac003a6ee7377..c24eee629c912d36488df0ff2932490f5ad2a381 100644 (file)
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-package check_test
+package fipstest
 
 import (
        . "crypto/internal/fips/check"
@@ -11,8 +11,8 @@ import (
        "internal/abi"
        "internal/asan"
        "internal/godebug"
+       "internal/testenv"
        "os"
-       "os/exec"
        "runtime"
        "testing"
        "unicode"
@@ -21,8 +21,8 @@ import (
 
 const enableFIPSTest = true
 
-func TestVerify(t *testing.T) {
-       if *Verified {
+func TestFIPSCheckVerify(t *testing.T) {
+       if Verified {
                t.Logf("verified")
                return
        }
@@ -44,7 +44,7 @@ func TestVerify(t *testing.T) {
                return
        }
 
-       cmd := exec.Command(os.Args[0], "-test.v")
+       cmd := testenv.Command(t, os.Args[0], "-test.v", "-test.run=TestFIPSCheck")
        cmd.Env = append(cmd.Environ(), "GODEBUG=fips140=on")
        out, err := cmd.CombinedOutput()
        if err != nil {
@@ -53,7 +53,7 @@ func TestVerify(t *testing.T) {
        t.Logf("exec'ed GODEBUG=fips140=on and succeeded:\n%s", out)
 }
 
-func TestInfo(t *testing.T) {
+func TestFIPSCheckInfo(t *testing.T) {
        if !enableFIPSTest {
                return
        }
similarity index 82%
rename from src/crypto/internal/fips/aes/gcm/cmac_test.go
rename to src/crypto/internal/fipstest/cmac_test.go
index d2418fd8d37736f372e061979178beca7cf34d62..05e421ffe937383a684a7f82fee87a8ce79256e1 100644 (file)
@@ -2,14 +2,12 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-package gcm_test
+package fipstest
 
 import (
        "bytes"
        "crypto/internal/fips/aes"
        "crypto/internal/fips/aes/gcm"
-       "encoding/hex"
-       "strings"
        "testing"
 )
 
@@ -47,13 +45,3 @@ func TestCMAC(t *testing.T) {
                }
        }
 }
-
-func decodeHex(t *testing.T, s string) []byte {
-       t.Helper()
-       s = strings.ReplaceAll(s, " ", "")
-       b, err := hex.DecodeString(s)
-       if err != nil {
-               t.Fatal(err)
-       }
-       return b
-}
diff --git a/src/crypto/internal/fipstest/ctrdrbg_test.go b/src/crypto/internal/fipstest/ctrdrbg_test.go
new file mode 100644 (file)
index 0000000..79efb39
--- /dev/null
@@ -0,0 +1,40 @@
+// 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 (
+       "bytes"
+       "crypto/internal/fips/drbg"
+       "crypto/internal/fips/subtle"
+       "testing"
+)
+
+func TestCounterDRBG(t *testing.T) {
+       // https://github.com/usnistgov/ACVP-Server/blob/fb44dce/gen-val/json-files/ctrDRBG-1.0/prompt.json#L4447-L4482
+
+       entropyInput := decodeHex(t, "9FCBB4CCC0135C484BDED061DA9FD70748682FE84166B97FF53F9AA1909B2E95D3D529C0F453B3AC575D12AA441CC5CD")
+       persoString := decodeHex(t, "2C9FED0B39556CDBE699EBCA2A0EC7EECB287E8744475050C572FA8AE9ED0A4A7D6F1CABF1C4278532FB20AF7D64BD32")
+       reseedEntropy := decodeHex(t, "913C0DA19B010EDDD55A7A4F3F713EEF5B1534D34360A7EC376AE71A6B340043CC7726F762CB853453F399B3A645062A")
+       reseedAdditional := decodeHex(t, "2D9D4EC141A22E6CD2F6EE4F6719CF6BDF95CFE50B8D5EA6C87D38B4B872706FFF80B0380BB90E9C42D11D6526E56C29")
+       additional1 := decodeHex(t, "A642F06D327828F3E84564A3E37D60C157073B95864CA07981B0189668A0D978CD5DC68F06801CEFF0DC839A312B028E")
+       additional2 := decodeHex(t, "9DB14BABFA9107C88BA92073C0B4A65E89147EA06D74B894142979482F452915B35B5636F9B8A951759735ADE7C8D5D1")
+       returnedBits := decodeHex(t, "F10C645683FF0131254052ED4C698122B46B563654C29D728AC191CA4AAEFE649EEFE4C6FC33B25BB739294DD5CF578099F856C98D98000CBF971F1E6EA900822FF8C110118F6520471744D3F8A3F5C7D568494240E57F5488AF9C9F9F4E7322F56CCD843C0DBFCE9170C02E205389420527F23EDB3369D9FCC5E34901B5BA4EB71B973FC7982FFE0899FF7FE53EE0C4F51A3EF93EF9C6D4D279DD7536F8776BE94AAA05E89EF6E6AEE8832B4B42FFCA5FB91EC0273F9EF945865512889B0C5EE141D1B38DF827D2A694835561628C6F9B093A01A835F07ADBB9E03FEBF93389E8F3B86E1E0ABF1F9958FA286AD995289C2F606D1A9043A166C1AFE8D00769C712650819C9068A4BD22717C98338395A7BA6E95B5178BFBF4EFB0F05A91713BA8BF2127A6BA1EDFA6D1CAB05C03EE0D2AFE1DA4EB8F2C579EC872FF4B602027EF4BDCF2F4B01423F8E600A13D7CACB6AB83263BA58F907694AF614A6724FD0E4C627A0D91DDC6716C697FACE6F4808A4F37B731DE4E0CD4766CEADAAAF47992505299C72AC1A6E9A8335B8D7E501B3841188D0DA4DE5267674444DC2B0CF9F010756FA865A25CA3F1B24C34E845B2259926B6A867A7684DE68A6137C4FB0F47A2E54AE9E6455BEBA0B0A9629644FE9E378EE95386443BA977124FFD1192E9F460684C7B09FA99F5F93F04F56FD7955E042187887CE696F1934017E458B16B5C9")
+
+       // We don't support personalization strings, but the pre-generated JSON
+       // vectors always use them, so just pre-mix them.
+       var seed [drbg.SeedSize]byte
+       subtle.XORBytes(seed[:], entropyInput, persoString)
+       c := drbg.NewCounter(&seed)
+
+       c.Reseed((*[48]byte)(reseedEntropy), (*[48]byte)(reseedAdditional))
+
+       buf := make([]byte, len(returnedBits))
+       c.Generate(buf, (*[48]byte)(additional1))
+
+       c.Generate(buf, (*[48]byte)(additional2))
+       if !bytes.Equal(buf, returnedBits) {
+               t.Errorf("unexpected output:\n%x\n%x", buf, returnedBits)
+       }
+}
diff --git a/src/crypto/internal/fipstest/fips_test.go b/src/crypto/internal/fipstest/fips_test.go
new file mode 100644 (file)
index 0000000..7390ac6
--- /dev/null
@@ -0,0 +1,28 @@
+// 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 collects external tests that would ordinarily live in
+// crypto/internal/fips/... packages. That tree gets snapshot at each
+// validation, while we want tests to evolve and still apply to all versions of
+// the module. Also, we can't fix failing tests in a module snapshot, so we need
+// to either minimize, skip, or remove them. Finally, the module needs to avoid
+// importing internal packages like testenv and cryptotest to avoid locking in
+// their APIs.
+package fipstest
+
+import (
+       "encoding/hex"
+       "strings"
+       "testing"
+)
+
+func decodeHex(t *testing.T, s string) []byte {
+       t.Helper()
+       s = strings.ReplaceAll(s, " ", "")
+       b, err := hex.DecodeString(s)
+       if err != nil {
+               t.Fatal(err)
+       }
+       return b
+}
similarity index 99%
rename from src/crypto/internal/fips/hkdf/hkdf_test.go
rename to src/crypto/internal/fipstest/hkdf_test.go
index 6bb2c6bc4ad39e5a2b55cd36a23d4f1ad12b050a..a624af33ab4997f980bfac30f3164496c35d17f5 100644 (file)
@@ -1,7 +1,10 @@
 // Copyright 2014 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 hkdf_test
+
+package fipstest_test
+
+// TODO(fips, #61477): move this to crypto/hkdf once it exists.
 
 import (
        "bytes"
similarity index 99%
rename from src/crypto/internal/fips/indicator_test.go
rename to src/crypto/internal/fipstest/indicator_test.go
index 8134b609c9260d2096c46fb6b56f507503ebccdc..a193959edf48160b3080623466b2b87e511a4a37 100644 (file)
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-package fips_test
+package fipstest
 
 import (
        "crypto/internal/fips"
similarity index 95%
rename from src/crypto/internal/fips/mlkem/mlkem_test.go
rename to src/crypto/internal/fipstest/mlkem_test.go
index f852fb6eb28792fc05fd444493f15fa6a837d884..d9a43034f37f10bdab1014b22afe85ca8d4f854e 100644 (file)
@@ -2,10 +2,13 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-package mlkem
+package fipstest_test
+
+// TODO(fips, #70122): move this to crypto/mlkem once it exists.
 
 import (
        "bytes"
+       . "crypto/internal/fips/mlkem"
        "crypto/internal/fips/sha3"
        "crypto/rand"
        _ "embed"
@@ -176,7 +179,7 @@ func TestAccumulated(t *testing.T) {
        s := sha3.NewShake128()
        o := sha3.NewShake128()
        seed := make([]byte, SeedSize)
-       var msg [messageSize]byte
+       var msg [32]byte
        ct1 := make([]byte, CiphertextSize768)
 
        for i := 0; i < n; i++ {
@@ -189,7 +192,7 @@ func TestAccumulated(t *testing.T) {
                o.Write(ek.Bytes())
 
                s.Read(msg[:])
-               ct, k := kemEncaps(nil, ek, &msg)
+               ct, k := ek.EncapsulateInternal(&msg)
                o.Write(ct)
                o.Write(k)
 
@@ -218,13 +221,12 @@ func TestAccumulated(t *testing.T) {
 var sink byte
 
 func BenchmarkKeyGen(b *testing.B) {
-       var dk DecapsulationKey768
        var d, z [32]byte
        rand.Read(d[:])
        rand.Read(z[:])
        b.ResetTimer()
        for i := 0; i < b.N; i++ {
-               kemKeyGen(&dk, &d, &z)
+               dk := GenerateKeyInternal768(&d, &z)
                sink ^= dk.EncapsulationKey().Bytes()[0]
        }
 }
@@ -232,21 +234,20 @@ func BenchmarkKeyGen(b *testing.B) {
 func BenchmarkEncaps(b *testing.B) {
        seed := make([]byte, SeedSize)
        rand.Read(seed)
-       var m [messageSize]byte
+       var m [32]byte
        rand.Read(m[:])
        dk, err := NewDecapsulationKey768(seed)
        if err != nil {
                b.Fatal(err)
        }
        ekBytes := dk.EncapsulationKey().Bytes()
-       var c [CiphertextSize768]byte
        b.ResetTimer()
        for i := 0; i < b.N; i++ {
                ek, err := NewEncapsulationKey768(ekBytes)
                if err != nil {
                        b.Fatal(err)
                }
-               c, K := kemEncaps(&c, ek, &m)
+               c, K := ek.EncapsulateInternal(&m)
                sink ^= c[0] ^ K[0]
        }
 }
@@ -260,7 +261,7 @@ func BenchmarkDecaps(b *testing.B) {
        c, _ := ek.Encapsulate()
        b.ResetTimer()
        for i := 0; i < b.N; i++ {
-               K := kemDecaps(dk, (*[CiphertextSize768]byte)(c))
+               K, _ := dk.Decapsulate(c)
                sink ^= K[0]
        }
 }
similarity index 98%
rename from src/crypto/internal/fips/sha3/sha3_test.go
rename to src/crypto/internal/fipstest/sha3_test.go
index 42b5d8ea9866c499a1c1930db8e0b1daeaffd216..c9b0e2729d92d527f42095b4b2103216b6e0741f 100644 (file)
@@ -2,7 +2,9 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-package sha3_test
+package fipstest_test
+
+// TODO(fips, #69982): move to the crypto/sha3 package once it exists.
 
 import (
        "bytes"
@@ -18,8 +20,6 @@ import (
        "testing"
 )
 
-// TODO(fips): move tests to the stdlib crypto/sha3 package.
-
 // Sum224 returns the SHA3-224 digest of the data.
 func Sum224(data []byte) (digest [28]byte) {
        h := New224()
@@ -366,7 +366,7 @@ func testClone(t *testing.T) {
        }
 }
 
-var sink byte
+var sinkSHA3 byte
 
 func TestAllocations(t *testing.T) {
        cryptotest.SkipTestAllocations(t)
@@ -377,7 +377,7 @@ func TestAllocations(t *testing.T) {
                        h.Write(b)
                        out := make([]byte, 0, 32)
                        out = h.Sum(out)
-                       sink ^= out[0]
+                       sinkSHA3 ^= out[0]
                }); allocs > 0 {
                        t.Errorf("expected zero allocations, got %0.1f", allocs)
                }
@@ -389,9 +389,9 @@ func TestAllocations(t *testing.T) {
                        h.Write(b)
                        out := make([]byte, 0, 32)
                        out = h.Sum(out)
-                       sink ^= out[0]
+                       sinkSHA3 ^= out[0]
                        h.Read(out)
-                       sink ^= out[0]
+                       sinkSHA3 ^= out[0]
                }); allocs > 0 {
                        t.Errorf("expected zero allocations, got %0.1f", allocs)
                }
@@ -400,7 +400,7 @@ func TestAllocations(t *testing.T) {
                if allocs := testing.AllocsPerRun(10, func() {
                        b := []byte("ABC")
                        out := Sum256(b)
-                       sink ^= out[0]
+                       sinkSHA3 ^= out[0]
                }); allocs > 0 {
                        t.Errorf("expected zero allocations, got %0.1f", allocs)
                }
similarity index 98%
rename from src/crypto/internal/fips/ssh/kdf_test.go
rename to src/crypto/internal/fipstest/sshkdf_test.go
index e42cd3ae194b2e598f4f6e31bb0c1d53786e13a5..b942ca86a241ca6ce2ac269daf7299d16d39d533 100644 (file)
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-package ssh_test
+package fipstest
 
 import (
        "bytes"
@@ -12,7 +12,7 @@ import (
        "testing"
 )
 
-func TestACVPVector(t *testing.T) {
+func TestSSHACVPVector(t *testing.T) {
        // https://github.com/usnistgov/ACVP-Server/blob/3a7333f638/gen-val/json-files/kdf-components-ssh-1.0/prompt.json#L910-L915
        K := fromHex("0000010100E534CD9780786AF19994DD68C3FD7FE1E1F77C3938B2005C49B080CF88A63A44079774A36F23BA4D73470CB318C30524854D2F36BAB9A45AD73DBB3BC5DD39A547F62BC921052E102E37F3DD0CD79A04EB46ACC14B823B326096A89E33E8846624188BB3C8F16B320E7BB8F5EB05F080DCEE244A445DBED3A9F3BA8C373D8BE62CDFE2FC5876F30F90F01F0A55E5251B23E0DBBFCFB1450715E329BB00FB222E850DDB11201460B8AEF3FC8965D3B6D3AFBB885A6C11F308F10211B82EA2028C7A84DD0BB8D5D6AC3A48D0C2B93609269C585E03889DB3621993E7F7C09A007FB6B5C06FFA532B0DBF11F71F740D9CD8FAD2532E21B9423BF3D85EE4E396BE32")
        H := fromHex("8FB22F0864960DA5679FD377248E41C2D0390E5AB3BB7955A3B6C588FB75B20D")
similarity index 97%
rename from src/crypto/internal/fips/aes/gcm/ctrkdf_test.go
rename to src/crypto/internal/fipstest/xaes_test.go
index ec8cddbd98726340d54002f2448a8158a8bfe0ce..9e21428c97ccc547641f8b9a122a57be19bd77d7 100644 (file)
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-package gcm_test
+package fipstest
 
 import (
        "bytes"
@@ -16,7 +16,7 @@ import (
        "testing"
 )
 
-func TestAllocations(t *testing.T) {
+func TestXAESAllocations(t *testing.T) {
        if runtime.GOARCH == "ppc64" || runtime.GOARCH == "ppc64le" {
                t.Skip("Test reports non-zero allocation count. See issue #70448")
        }
@@ -85,7 +85,7 @@ func xaesOpen(dst, key, nonce, ciphertext, additionalData []byte) ([]byte, error
        return g.Open(dst, n, ciphertext, additionalData)
 }
 
-func TestVectors(t *testing.T) {
+func TestXAESVectors(t *testing.T) {
        key := bytes.Repeat([]byte{0x01}, 32)
        nonce := []byte("ABCDEFGHIJKLMNOPQRSTUVWX")
        plaintext := []byte("XAES-256-GCM")
@@ -114,7 +114,7 @@ func TestVectors(t *testing.T) {
        }
 }
 
-func TestAccumulated(t *testing.T) {
+func TestXAESAccumulated(t *testing.T) {
        iterations := 10_000
        expected := "e6b9edf2df6cec60c8cbd864e2211b597fb69a529160cd040d56c0c210081939"