//go:linkname fatal crypto/internal/fips.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.
+// failfipscast is a GODEBUG key allowing simulation of a CAST or PCT failure,
+// as required during FIPS 140-3 functional testing. The value is the whole name
+// of the target CAST or PCT.
var failfipscast = godebug.Value("#failfipscast")
-// 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.
+// CAST runs the named Cryptographic Algorithm Self-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
-// 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.
+// 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 from init(), also import the calling package from
-// crypto/internal/fipstest, while if calling it from key generation/importing, add
-// an invocation to fipstest.TestPCTs.
+// If a package p calls CAST from its init function, an import of p should also
+// be added to crypto/internal/fipstest. If a package p calls CAST on the first
+// use of the algorithm, an invocation of that algorithm should be added to
+// fipstest.TestConditionals.
func CAST(name string, f func() error) {
if strings.ContainsAny(name, ",#=:") {
panic("fips: invalid self-test name: " + name)
}
err := f()
- if failfipscast != "" && strings.Contains(name, failfipscast) {
- err = errors.New("simulated CAST/PCT failure")
+ if name == failfipscast {
+ err = errors.New("simulated CAST failure")
}
if err != nil {
fatal("FIPS 140-3 self-test failed: " + name + ": " + err.Error())
println("FIPS 140-3 self-test passed:", name)
}
}
+
+// PCT runs the named Pairwise Consistency Test (if operated in FIPS mode) and
+// returns any errors. If an error is returned, the key must not be used.
+//
+// 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.
+//
+// The name must not contain commas, colons, hashes, or equal signs.
+//
+// If a package p calls PCT during key generation, an invocation of that
+// function should be added to fipstest.TestConditionals.
+func PCT(name string, f func() error) error {
+ if strings.ContainsAny(name, ",#=:") {
+ panic("fips: invalid self-test name: " + name)
+ }
+ if !Enabled {
+ return nil
+ }
+
+ err := f()
+ if name == failfipscast {
+ err = errors.New("simulated PCT failure")
+ }
+ return err
+}
// Comment 1 goes out of its way to say that "the PCT shall be performed
// consistent [...], even if the underlying standard does not require a
// PCT". So we do it. And make ECDH nearly 50% slower (only) in FIPS mode.
- fips.CAST("ECDH PCT", func() error {
+ if err := fips.PCT("ECDH PCT", func() error {
p1, err := newPoint().ScalarBaseMult(key)
if err != nil {
return err
return errors.New("crypto/ecdh: public key does not match private key")
}
return nil
- })
+ }); err != nil {
+ panic(err)
+ }
return publicKey, nil
}
}
func fipsPCT[P Point[P]](c *Curve[P], k *PrivateKey) error {
- hash := testHash()
- sig, err := Sign(c, sha512.New, k, nil, hash)
- if err != nil {
- return err
- }
- return Verify(c, &k.pub, hash, sig)
+ return fips.PCT("ECDSA PCT", func() error {
+ hash := testHash()
+ sig, err := Sign(c, sha512.New, k, nil, hash)
+ if err != nil {
+ return err
+ }
+ return Verify(c, &k.pub, hash, sig)
+ })
}
var fipsSelfTest = sync.OnceFunc(func() {
return nil, err
}
priv := &PrivateKey{pub: *pub, d: d.Bytes(c.N)}
- fips.CAST("ECDSA PCT", func() error { return fipsPCT(c, priv) })
+ if err := fipsPCT(c, priv); err != nil {
+ // This can happen if the application went out of its way to make an
+ // ecdsa.PrivateKey with a mismatching PublicKey.
+ return nil, err
+ }
return priv, nil
}
},
d: k.Bytes(c.N),
}
- fips.CAST("ECDSA PCT", func() error { return fipsPCT(c, priv) })
+ if err := fipsPCT(c, priv); err != nil {
+ // This clearly can't happen, but FIPS 140-3 mandates that we check it.
+ panic(err)
+ }
return priv, nil
}
var z [32]byte
drbg.Read(z[:])
kemKeyGen1024(dk, &d, &z)
- fips.CAST("ML-KEM PCT", func() error { return kemPCT1024(dk) })
+ if err := fips.PCT("ML-KEM PCT", func() error { return kemPCT1024(dk) }); err != nil {
+ // This clearly can't happen, but FIPS 140-3 requires us to check.
+ panic(err)
+ }
fips.RecordApproved()
return dk, nil
}
d := (*[32]byte)(seed[:32])
z := (*[32]byte)(seed[32:])
kemKeyGen1024(dk, d, z)
- fips.CAST("ML-KEM PCT", func() error { return kemPCT1024(dk) })
+ if err := fips.PCT("ML-KEM PCT", func() error { return kemPCT1024(dk) }); err != nil {
+ // This clearly can't happen, but FIPS 140-3 requires us to check.
+ panic(err)
+ }
fips.RecordApproved()
return dk, nil
}
var z [32]byte
drbg.Read(z[:])
kemKeyGen(dk, &d, &z)
- fips.CAST("ML-KEM PCT", func() error { return kemPCT(dk) })
+ if err := fips.PCT("ML-KEM PCT", func() error { return kemPCT(dk) }); err != nil {
+ // This clearly can't happen, but FIPS 140-3 requires us to check.
+ panic(err)
+ }
fips.RecordApproved()
return dk, nil
}
d := (*[32]byte)(seed[:32])
z := (*[32]byte)(seed[32:])
kemKeyGen(dk, d, z)
- fips.CAST("ML-KEM PCT", func() error { return kemPCT(dk) })
+ if err := fips.PCT("ML-KEM PCT", func() error { return kemPCT(dk) }); err != nil {
+ // This clearly can't happen, but FIPS 140-3 requires us to check.
+ panic(err)
+ }
fips.RecordApproved()
return dk, nil
}
_ "crypto/internal/fips/hmac"
"crypto/internal/fips/mlkem"
"crypto/internal/fips/sha256"
- _ "crypto/internal/fips/sha256"
_ "crypto/internal/fips/sha3"
_ "crypto/internal/fips/sha512"
_ "crypto/internal/fips/tls12"
fipsDir := strings.TrimSpace(string(out))
t.Logf("FIPS module directory: %s", fipsDir)
- // Find all invocations of fips.CAST.
+ // Find all invocations of fips.CAST or fips.PCT.
allCASTs := make(map[string]struct{})
- castRe := regexp.MustCompile(`fips\.CAST\("([^"]+)"`)
+ castRe := regexp.MustCompile(`fips\.(CAST|PCT)\("([^"]+)"`)
if err := fs.WalkDir(os.DirFS(fipsDir), ".", func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
return err
}
for _, m := range castRe.FindAllSubmatch(data, -1) {
- allCASTs[string(m[1])] = struct{}{}
+ allCASTs[string(m[2])] = struct{}{}
}
return nil
}); err != nil {
if err == nil {
t.Error(err)
} else {
- t.Logf("CAST %s failed and caused the program to exit", name)
+ t.Logf("CAST/PCT %s failed and caused the program to exit or the test to fail", name)
t.Logf("%s", out)
}
if strings.Contains(string(out), "completed successfully") {
- t.Errorf("CAST %s failure did not stop the program", name)
+ t.Errorf("CAST/PCT %s failure did not stop the program", name)
}
})
}