]> Cypherpunks repositories - gostls13.git/commitdiff
crypto/internal/fips/mlkem: implement CAST, PCT, and service indicator
authorFilippo Valsorda <filippo@golang.org>
Sun, 10 Nov 2024 14:22:00 +0000 (15:22 +0100)
committerGopher Robot <gobot@golang.org>
Tue, 19 Nov 2024 20:43:08 +0000 (20:43 +0000)
For #69536

Change-Id: Id9d2f6553ab006d0d26986d22a4a756b9cf1bf71
Reviewed-on: https://go-review.googlesource.com/c/go/+/626936
Auto-Submit: Filippo Valsorda <filippo@golang.org>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Russ Cox <rsc@golang.org>
Reviewed-by: Roland Shoemaker <roland@golang.org>
Reviewed-by: Daniel McCarney <daniel@binaryparadox.net>
src/crypto/internal/fips/cast.go
src/crypto/internal/fips/cast_external_test.go
src/crypto/internal/fips/mlkem/cast.go [new file with mode: 0644]
src/crypto/internal/fips/mlkem/generate1024.go
src/crypto/internal/fips/mlkem/mlkem1024.go
src/crypto/internal/fips/mlkem/mlkem768.go
src/crypto/internal/fips/mlkem/mlkem_test.go

index 17c92c1c3e33f2fd61a6b8a0ab27f40411400889..1afbfcd2450884008e1212551af9363d50991e46 100644 (file)
@@ -24,16 +24,21 @@ 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 (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)
@@ -47,7 +52,7 @@ func CAST(name string, f func() error) {
 
        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())
index 3c5007ff8d59e6c46f78699904e85d1886318893..0b895659f0e8f193ef5f6d4bbd25372a67e611e1 100644 (file)
@@ -17,6 +17,7 @@ import (
        _ "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"
@@ -29,6 +30,9 @@ func TestCAST(t *testing.T) {
                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)
diff --git a/src/crypto/internal/fips/mlkem/cast.go b/src/crypto/internal/fips/mlkem/cast.go
new file mode 100644 (file)
index 0000000..f712fbb
--- /dev/null
@@ -0,0 +1,52 @@
+// 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
+       })
+}
index 7ed68debdbdef62ecf9c04756667b4532a454c24..94f22d54f995429f93b1367e1736ea32e6ab1884 100644 (file)
@@ -44,6 +44,7 @@ var replacements = map[string]string{
        "generateKey":    "generateKey1024",
 
        "kemKeyGen": "kemKeyGen1024",
+       "kemPCT":    "kemPCT1024",
 
        "encodingSize4":             "encodingSize5",
        "encodingSize10":            "encodingSize11",
index c77dae8f743ba2d9ea118a2b22c160bc8e1d7d4c..6167c8bb8fe864c39bcaa66dc2c7782cc8611048 100644 (file)
@@ -3,6 +3,7 @@
 package mlkem
 
 import (
+       "crypto/internal/fips"
        "crypto/internal/fips/drbg"
        "crypto/internal/fips/sha3"
        "crypto/internal/fips/subtle"
@@ -81,15 +82,17 @@ type decryptionKey1024 struct {
 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
@@ -106,7 +109,9 @@ func newKeyFromSeed1024(dk *DecapsulationKey1024, seed []byte) (*DecapsulationKe
        }
        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.
@@ -114,10 +119,9 @@ func newKeyFromSeed1024(dk *DecapsulationKey1024, seed []byte) (*DecapsulationKe
 // 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
 
@@ -159,8 +163,27 @@ func kemKeyGen1024(dk *DecapsulationKey1024, d, z *[32]byte) *DecapsulationKey10
        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
@@ -185,6 +208,7 @@ func (ek *EncapsulationKey1024) encapsulate(cc *[CiphertextSize1024]byte) (ciphe
 //
 // 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{}
        }
@@ -300,6 +324,7 @@ func (dk *DecapsulationKey1024) Decapsulate(ciphertext []byte) (sharedKey []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[:])
index 8cd6fffbcd13acf979c974d50261be292077e689..6c5cb85535ccb34ccf95fd2fc75abf8ced178e99 100644 (file)
@@ -24,6 +24,7 @@ package mlkem
 //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"
@@ -138,15 +139,17 @@ type decryptionKey struct {
 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
@@ -163,7 +166,9 @@ func newKeyFromSeed(dk *DecapsulationKey768, seed []byte) (*DecapsulationKey768,
        }
        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.
@@ -171,10 +176,9 @@ func newKeyFromSeed(dk *DecapsulationKey768, seed []byte) (*DecapsulationKey768,
 // 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
 
@@ -216,8 +220,27 @@ func kemKeyGen(dk *DecapsulationKey768, d, z *[32]byte) *DecapsulationKey768 {
        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
@@ -242,6 +265,7 @@ func (ek *EncapsulationKey768) encapsulate(cc *[CiphertextSize768]byte) (ciphert
 //
 // 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{}
        }
@@ -357,6 +381,7 @@ func (dk *DecapsulationKey768) Decapsulate(ciphertext []byte) (sharedKey []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[:])
index acd8f4821b963afcb94c6d738bec30056a16a4ef..f852fb6eb28792fc05fd444493f15fa6a837d884 100644 (file)
@@ -224,7 +224,7 @@ func BenchmarkKeyGen(b *testing.B) {
        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]
        }
 }