// testingOnlyCASTHook is called during tests with each CAST name.
var testingOnlyCASTHook func(string)
-// 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.
+// 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.
//
-// 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.
+// 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.
//
// The name must not contain commas, colons, hashes, or equal signs.
//
-// When calling this function, also add the calling package to cast_external_test.go.
+// 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.
func CAST(name string, f func() error) {
if strings.ContainsAny(name, ",#=:") {
panic("fips: invalid self-test name: " + name)
err := f()
if failfipscast.Value() != "" && strings.Contains(name, failfipscast.Value()) {
- err = errors.New("simulated CAST failure")
+ err = errors.New("simulated CAST/PCT failure")
}
if err != nil {
fatal("FIPS 140-3 self-test failed: " + name + ": " + err.Error())
_ "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"
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)
--- /dev/null
+// 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 mlkem
+
+import (
+ "bytes"
+ "crypto/internal/fips"
+ "errors"
+)
+
+func init() {
+ fips.CAST("ML-KEM-768", func() error {
+ var d = &[32]byte{
+ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
+ 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10,
+ 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
+ 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20,
+ }
+ var z = &[32]byte{
+ 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
+ 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30,
+ 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38,
+ 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40,
+ }
+ var m = &[32]byte{
+ 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48,
+ 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50,
+ 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58,
+ 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60,
+ }
+ var K = []byte{
+ 0x55, 0x01, 0xfc, 0x52, 0x3b, 0x74, 0x5f, 0x41,
+ 0x76, 0x2a, 0x18, 0x8d, 0xe4, 0x4a, 0x59, 0xb9,
+ 0x20, 0xf4, 0x30, 0x14, 0x62, 0x04, 0xee, 0x4e,
+ 0x79, 0x37, 0x32, 0x39, 0x6d, 0xf7, 0xaa, 0x48,
+ }
+ dk := &DecapsulationKey768{}
+ kemKeyGen(dk, d, z)
+ ek := dk.EncapsulationKey()
+ c, Ke := kemEncaps(nil, ek, m)
+ Kd, err := dk.Decapsulate(c)
+ if err != nil {
+ return err
+ }
+ if !bytes.Equal(Ke, K) || !bytes.Equal(Kd, K) {
+ return errors.New("unexpected result")
+ }
+ return nil
+ })
+}
"generateKey": "generateKey1024",
"kemKeyGen": "kemKeyGen1024",
+ "kemPCT": "kemPCT1024",
"encodingSize4": "encodingSize5",
"encodingSize10": "encodingSize11",
package mlkem
import (
+ "crypto/internal/fips"
"crypto/internal/fips/drbg"
"crypto/internal/fips/sha3"
"crypto/internal/fips/subtle"
func GenerateKey1024() (*DecapsulationKey1024, error) {
// The actual logic is in a separate function to outline this allocation.
dk := &DecapsulationKey1024{}
- return generateKey1024(dk), nil
+ return generateKey1024(dk)
}
-func generateKey1024(dk *DecapsulationKey1024) *DecapsulationKey1024 {
+func generateKey1024(dk *DecapsulationKey1024) (*DecapsulationKey1024, error) {
var d [32]byte
drbg.Read(d[:])
var z [32]byte
drbg.Read(z[:])
- return kemKeyGen1024(dk, &d, &z)
+ kemKeyGen1024(dk, &d, &z)
+ fips.CAST("ML-KEM PCT", func() error { return kemPCT1024(dk) })
+ return dk, nil
}
// NewDecapsulationKey1024 parses a decapsulation key from a 64-byte
}
d := (*[32]byte)(seed[:32])
z := (*[32]byte)(seed[32:])
- return kemKeyGen1024(dk, d, z), nil
+ kemKeyGen1024(dk, d, z)
+ fips.CAST("ML-KEM PCT", func() error { return kemPCT1024(dk) })
+ return dk, nil
}
// kemKeyGen1024 generates a decapsulation key.
// It implements ML-KEM.KeyGen_internal according to FIPS 203, Algorithm 16, and
// 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) *DecapsulationKey1024 {
- if dk == nil {
- dk = &DecapsulationKey1024{}
- }
+func kemKeyGen1024(dk *DecapsulationKey1024, d, z *[32]byte) {
+ fips.RecordApproved()
+
dk.d = *d
dk.z = *z
ek := dk.EncapsulationKey().Bytes()
H.Write(ek)
H.Sum(dk.h[:0])
+}
- return dk
+// kemPCT1024 performs a Pairwise Consistency Test per FIPS 140-3 IG 10.3.A
+// Additional Comment 1: "For key pairs generated for use with approved KEMs in
+// FIPS 203, the PCT shall consist of applying the encapsulation key ek to
+// encapsulate a shared secret K leading to ciphertext c, and then applying
+// decapsulation key dk to retrieve the same shared secret K. The PCT passes if
+// the two shared secret K values are equal. The PCT shall be performed either
+// when keys are generated/imported, prior to the first exportation, or prior to
+// the first operational use (if not exported before the first use)."
+func kemPCT1024(dk *DecapsulationKey1024) error {
+ ek := dk.EncapsulationKey()
+ c, K := ek.Encapsulate()
+ K1, err := dk.Decapsulate(c)
+ if err != nil {
+ return err
+ }
+ if subtle.ConstantTimeCompare(K, K1) != 1 {
+ return errors.New("mlkem: PCT failed")
+ }
+ return nil
}
// Encapsulate generates a shared key and an associated ciphertext from an
//
// 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{}
}
//
// It implements ML-KEM.Decaps_internal according to FIPS 203, Algorithm 18.
func kemDecaps1024(dk *DecapsulationKey1024, c *[CiphertextSize1024]byte) (K []byte) {
+ fips.RecordApproved()
m := pkeDecrypt1024(&dk.decryptionKey1024, c)
g := sha3.New512()
g.Write(m[:])
//go:generate go run generate1024.go -input mlkem768.go -output mlkem1024.go
import (
+ "crypto/internal/fips"
"crypto/internal/fips/drbg"
"crypto/internal/fips/sha3"
"crypto/internal/fips/subtle"
func GenerateKey768() (*DecapsulationKey768, error) {
// The actual logic is in a separate function to outline this allocation.
dk := &DecapsulationKey768{}
- return generateKey(dk), nil
+ return generateKey(dk)
}
-func generateKey(dk *DecapsulationKey768) *DecapsulationKey768 {
+func generateKey(dk *DecapsulationKey768) (*DecapsulationKey768, error) {
var d [32]byte
drbg.Read(d[:])
var z [32]byte
drbg.Read(z[:])
- return kemKeyGen(dk, &d, &z)
+ kemKeyGen(dk, &d, &z)
+ fips.CAST("ML-KEM PCT", func() error { return kemPCT(dk) })
+ return dk, nil
}
// NewDecapsulationKey768 parses a decapsulation key from a 64-byte
}
d := (*[32]byte)(seed[:32])
z := (*[32]byte)(seed[32:])
- return kemKeyGen(dk, d, z), nil
+ kemKeyGen(dk, d, z)
+ fips.CAST("ML-KEM PCT", func() error { return kemPCT(dk) })
+ return dk, nil
}
// kemKeyGen generates a decapsulation key.
// It implements ML-KEM.KeyGen_internal according to FIPS 203, Algorithm 16, and
// 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) *DecapsulationKey768 {
- if dk == nil {
- dk = &DecapsulationKey768{}
- }
+func kemKeyGen(dk *DecapsulationKey768, d, z *[32]byte) {
+ fips.RecordApproved()
+
dk.d = *d
dk.z = *z
ek := dk.EncapsulationKey().Bytes()
H.Write(ek)
H.Sum(dk.h[:0])
+}
- return dk
+// kemPCT performs a Pairwise Consistency Test per FIPS 140-3 IG 10.3.A
+// Additional Comment 1: "For key pairs generated for use with approved KEMs in
+// FIPS 203, the PCT shall consist of applying the encapsulation key ek to
+// encapsulate a shared secret K leading to ciphertext c, and then applying
+// decapsulation key dk to retrieve the same shared secret K. The PCT passes if
+// the two shared secret K values are equal. The PCT shall be performed either
+// when keys are generated/imported, prior to the first exportation, or prior to
+// the first operational use (if not exported before the first use)."
+func kemPCT(dk *DecapsulationKey768) error {
+ ek := dk.EncapsulationKey()
+ c, K := ek.Encapsulate()
+ K1, err := dk.Decapsulate(c)
+ if err != nil {
+ return err
+ }
+ if subtle.ConstantTimeCompare(K, K1) != 1 {
+ return errors.New("mlkem: PCT failed")
+ }
+ return nil
}
// Encapsulate generates a shared key and an associated ciphertext from an
//
// 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{}
}
//
// It implements ML-KEM.Decaps_internal according to FIPS 203, Algorithm 18.
func kemDecaps(dk *DecapsulationKey768, c *[CiphertextSize768]byte) (K []byte) {
+ fips.RecordApproved()
m := pkeDecrypt(&dk.decryptionKey, c)
g := sha3.New512()
g.Write(m[:])
rand.Read(z[:])
b.ResetTimer()
for i := 0; i < b.N; i++ {
- dk := kemKeyGen(&dk, &d, &z)
+ kemKeyGen(&dk, &d, &z)
sink ^= dk.EncapsulationKey().Bytes()[0]
}
}