lib/fips140: re-seal v1.0.0

Exceptionally, we decided to make a compliance-related change following
CMVP's updated Implementation Guidance on September 2nd.

The Security Policy will be updated to reflect the new zip hash.

mkzip.go has been modified to accept versions of the form vX.Y.Z-hash,
where the -hash suffix is ignored for fips140.Version() but used to
name the zip file and the unpacked cache directory.

The new zip is generated with

	go run ../../src/cmd/go/internal/fips140/mkzip.go -b c2097c7c v1.0.0-c2097c7c

from c2097c7c which is the current release-branch.go1.24 head.

The full diff between the zip file contents is included below.

For #74947
Updates #69536


$ diff -ru golang.org/fips140@v1.0.0/fips140/v1.0.0/ golang.org/fips140@v1.0.0-c2097c7c/fips140/v1.0.0-c2097c7c

diff -ru golang.org/fips140@v1.0.0/fips140/v1.0.0/cast.go golang.org/fips140@v1.0.0-c2097c7c/fips140/v1.0.0-c2097c7c/cast.go
--- golang.org/fips140@v1.0.0/fips140/v1.0.0/cast.go	1980-01-10 00:00:00.000000000 +0100
+++ golang.org/fips140@v1.0.0-c2097c7c/fips140/v1.0.0-c2097c7c/cast.go	1980-01-10 00:00:00.000000000 +0100
@@ -56,9 +56,10 @@
 }

 // 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.
+// aborts the program (stopping the module input/output and entering the "error
+// state") if the test fails.
 //
-// PCTs are mandatory for every key pair that is generated/imported, including
+// PCTs are mandatory for every generated (but not imported) key pair, including
 // ephemeral keys (which effectively doubles the cost of key establishment). See
 // Implementation Guidance 10.3.A Additional Comment 1.
 //
@@ -66,17 +67,23 @@
 //
 // 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 {
+func PCT(name string, f func() error) {
 	if strings.ContainsAny(name, ",#=:") {
 		panic("fips: invalid self-test name: " + name)
 	}
 	if !Enabled {
-		return nil
+		return
 	}

 	err := f()
 	if name == failfipscast {
 		err = errors.New("simulated PCT failure")
 	}
-	return err
+	if err != nil {
+		fatal("FIPS 140-3 self-test failed: " + name + ": " + err.Error())
+		panic("unreachable")
+	}
+	if debug {
+		println("FIPS 140-3 PCT passed:", name)
+	}
 }
diff -ru golang.org/fips140@v1.0.0/fips140/v1.0.0/ecdh/ecdh.go golang.org/fips140@v1.0.0-c2097c7c/fips140/v1.0.0-c2097c7c/ecdh/ecdh.go
--- golang.org/fips140@v1.0.0/fips140/v1.0.0/ecdh/ecdh.go	1980-01-10 00:00:00.000000000 +0100
+++ golang.org/fips140@v1.0.0-c2097c7c/fips140/v1.0.0-c2097c7c/ecdh/ecdh.go	1980-01-10 00:00:00.000000000 +0100
@@ -161,6 +161,27 @@
 		if err != nil {
 			continue
 		}
+
+		// A "Pairwise Consistency Test" makes no sense if we just generated the
+		// public key from an ephemeral private key. Moreover, there is no way to
+		// check it aside from redoing the exact same computation again. SP 800-56A
+		// Rev. 3, Section 5.6.2.1.4 acknowledges that, and doesn't require it.
+		// However, ISO 19790:2012, Section 7.10.3.3 has a blanket requirement for a
+		// PCT for all generated keys (AS10.35) and FIPS 140-3 IG 10.3.A, Additional
+		// 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.
+		fips140.PCT("ECDH PCT", func() error {
+			p1, err := c.newPoint().ScalarBaseMult(privateKey.d)
+			if err != nil {
+				return err
+			}
+			if !bytes.Equal(p1.Bytes(), privateKey.pub.q) {
+				return errors.New("crypto/ecdh: public key does not match private key")
+			}
+			return nil
+		})
+
 		return privateKey, nil
 	}
 }
@@ -188,28 +209,6 @@
 		panic("crypto/ecdh: internal error: public key is the identity element")
 	}

-	// A "Pairwise Consistency Test" makes no sense if we just generated the
-	// public key from an ephemeral private key. Moreover, there is no way to
-	// check it aside from redoing the exact same computation again. SP 800-56A
-	// Rev. 3, Section 5.6.2.1.4 acknowledges that, and doesn't require it.
-	// However, ISO 19790:2012, Section 7.10.3.3 has a blanket requirement for a
-	// PCT for all generated keys (AS10.35) and FIPS 140-3 IG 10.3.A, Additional
-	// 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.
-	if err := fips140.PCT("ECDH PCT", func() error {
-		p1, err := c.newPoint().ScalarBaseMult(key)
-		if err != nil {
-			return err
-		}
-		if !bytes.Equal(p1.Bytes(), publicKey) {
-			return errors.New("crypto/ecdh: public key does not match private key")
-		}
-		return nil
-	}); err != nil {
-		panic(err)
-	}
-
 	k := &PrivateKey{d: bytes.Clone(key), pub: PublicKey{curve: c.curve, q: publicKey}}
 	return k, nil
 }
diff -ru golang.org/fips140@v1.0.0/fips140/v1.0.0/ecdsa/cast.go golang.org/fips140@v1.0.0-c2097c7c/fips140/v1.0.0-c2097c7c/ecdsa/cast.go
--- golang.org/fips140@v1.0.0/fips140/v1.0.0/ecdsa/cast.go	1980-01-10 00:00:00.000000000 +0100
+++ golang.org/fips140@v1.0.0-c2097c7c/fips140/v1.0.0-c2097c7c/ecdsa/cast.go	1980-01-10 00:00:00.000000000 +0100
@@ -51,8 +51,8 @@
 	}
 }

-func fipsPCT[P Point[P]](c *Curve[P], k *PrivateKey) error {
-	return fips140.PCT("ECDSA PCT", func() error {
+func fipsPCT[P Point[P]](c *Curve[P], k *PrivateKey) {
+	fips140.PCT("ECDSA PCT", func() error {
 		hash := testHash()
 		drbg := newDRBG(sha512.New, k.d, bits2octets(P256(), hash), nil)
 		sig, err := sign(c, k, drbg, hash)
diff -ru golang.org/fips140@v1.0.0/fips140/v1.0.0/ecdsa/ecdsa.go golang.org/fips140@v1.0.0-c2097c7c/fips140/v1.0.0-c2097c7c/ecdsa/ecdsa.go
--- golang.org/fips140@v1.0.0/fips140/v1.0.0/ecdsa/ecdsa.go	1980-01-10 00:00:00.000000000 +0100
+++ golang.org/fips140@v1.0.0-c2097c7c/fips140/v1.0.0-c2097c7c/ecdsa/ecdsa.go	1980-01-10 00:00:00.000000000 +0100
@@ -166,11 +166,6 @@
 		return nil, err
 	}
 	priv := &PrivateKey{pub: *pub, d: d.Bytes(c.N)}
-	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
 }

@@ -203,10 +198,7 @@
 		},
 		d: k.Bytes(c.N),
 	}
-	if err := fipsPCT(c, priv); err != nil {
-		// This clearly can't happen, but FIPS 140-3 mandates that we check it.
-		panic(err)
-	}
+	fipsPCT(c, priv)
 	return priv, nil
 }

diff -ru golang.org/fips140@v1.0.0/fips140/v1.0.0/ecdsa/hmacdrbg.go golang.org/fips140@v1.0.0-c2097c7c/fips140/v1.0.0-c2097c7c/ecdsa/hmacdrbg.go
--- golang.org/fips140@v1.0.0/fips140/v1.0.0/ecdsa/hmacdrbg.go	1980-01-10 00:00:00.000000000 +0100
+++ golang.org/fips140@v1.0.0-c2097c7c/fips140/v1.0.0-c2097c7c/ecdsa/hmacdrbg.go	1980-01-10 00:00:00.000000000 +0100
@@ -121,7 +121,7 @@
 //
 // This should only be used for ACVP testing. hmacDRBG is not intended to be
 // used directly.
-func TestingOnlyNewDRBG(hash func() fips140.Hash, entropy, nonce []byte, s []byte) *hmacDRBG {
+func TestingOnlyNewDRBG[H fips140.Hash](hash func() H, entropy, nonce []byte, s []byte) *hmacDRBG {
 	return newDRBG(hash, entropy, nonce, plainPersonalizationString(s))
 }

diff -ru golang.org/fips140@v1.0.0/fips140/v1.0.0/ed25519/cast.go golang.org/fips140@v1.0.0-c2097c7c/fips140/v1.0.0-c2097c7c/ed25519/cast.go
--- golang.org/fips140@v1.0.0/fips140/v1.0.0/ed25519/cast.go	1980-01-10 00:00:00.000000000 +0100
+++ golang.org/fips140@v1.0.0-c2097c7c/fips140/v1.0.0-c2097c7c/ed25519/cast.go	1980-01-10 00:00:00.000000000 +0100
@@ -12,8 +12,8 @@
 	"sync"
 )

-func fipsPCT(k *PrivateKey) error {
-	return fips140.PCT("Ed25519 sign and verify PCT", func() error {
+func fipsPCT(k *PrivateKey) {
+	fips140.PCT("Ed25519 sign and verify PCT", func() error {
 		return pairwiseTest(k)
 	})
 }
diff -ru golang.org/fips140@v1.0.0/fips140/v1.0.0/ed25519/ed25519.go golang.org/fips140@v1.0.0-c2097c7c/fips140/v1.0.0-c2097c7c/ed25519/ed25519.go
--- golang.org/fips140@v1.0.0/fips140/v1.0.0/ed25519/ed25519.go	1980-01-10 00:00:00.000000000 +0100
+++ golang.org/fips140@v1.0.0-c2097c7c/fips140/v1.0.0-c2097c7c/ed25519/ed25519.go	1980-01-10 00:00:00.000000000 +0100
@@ -69,10 +69,7 @@
 	fips140.RecordApproved()
 	drbg.Read(priv.seed[:])
 	precomputePrivateKey(priv)
-	if err := fipsPCT(priv); err != nil {
-		// This clearly can't happen, but FIPS 140-3 requires that we check.
-		panic(err)
-	}
+	fipsPCT(priv)
 	return priv, nil
 }

@@ -88,10 +85,6 @@
 	}
 	copy(priv.seed[:], seed)
 	precomputePrivateKey(priv)
-	if err := fipsPCT(priv); err != nil {
-		// This clearly can't happen, but FIPS 140-3 requires that we check.
-		panic(err)
-	}
 	return priv, nil
 }

@@ -137,12 +130,6 @@

 	copy(priv.prefix[:], h[32:])

-	if err := fipsPCT(priv); err != nil {
-		// This can happen if the application messed with the private key
-		// encoding, and the public key doesn't match the seed anymore.
-		return nil, err
-	}
-
 	return priv, nil
 }

diff -ru golang.org/fips140@v1.0.0/fips140/v1.0.0/fips140.go golang.org/fips140@v1.0.0-c2097c7c/fips140/v1.0.0-c2097c7c/fips140.go
--- golang.org/fips140@v1.0.0/fips140/v1.0.0/fips140.go	1980-01-10 00:00:00.000000000 +0100
+++ golang.org/fips140@v1.0.0-c2097c7c/fips140/v1.0.0-c2097c7c/fips140.go	1980-01-10 00:00:00.000000000 +0100
@@ -62,6 +62,10 @@
 	return "Go Cryptographic Module"
 }

+// Version returns the formal version (such as "v1.0.0") if building against a
+// frozen module with GOFIPS140. Otherwise, it returns "latest".
 func Version() string {
-	return "v1.0"
+	// This return value is replaced by mkzip.go, it must not be changed or
+	// moved to a different file.
+	return "v1.0.0"
 }
diff -ru golang.org/fips140@v1.0.0/fips140/v1.0.0/mlkem/mlkem1024.go golang.org/fips140@v1.0.0-c2097c7c/fips140/v1.0.0-c2097c7c/mlkem/mlkem1024.go
--- golang.org/fips140@v1.0.0/fips140/v1.0.0/mlkem/mlkem1024.go	1980-01-10 00:00:00.000000000 +0100
+++ golang.org/fips140@v1.0.0-c2097c7c/fips140/v1.0.0-c2097c7c/mlkem/mlkem1024.go	1980-01-10 00:00:00.000000000 +0100
@@ -118,10 +118,7 @@
 	var z [32]byte
 	drbg.Read(z[:])
 	kemKeyGen1024(dk, &d, &z)
-	if err := fips140.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)
-	}
+	fips140.PCT("ML-KEM PCT", func() error { return kemPCT1024(dk) })
 	fips140.RecordApproved()
 	return dk, nil
 }
@@ -149,10 +146,6 @@
 	d := (*[32]byte)(seed[:32])
 	z := (*[32]byte)(seed[32:])
 	kemKeyGen1024(dk, d, z)
-	if err := fips140.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)
-	}
 	fips140.RecordApproved()
 	return dk, nil
 }
diff -ru golang.org/fips140@v1.0.0/fips140/v1.0.0/mlkem/mlkem768.go golang.org/fips140@v1.0.0-c2097c7c/fips140/v1.0.0-c2097c7c/mlkem/mlkem768.go
--- golang.org/fips140@v1.0.0/fips140/v1.0.0/mlkem/mlkem768.go	1980-01-10 00:00:00.000000000 +0100
+++ golang.org/fips140@v1.0.0-c2097c7c/fips140/v1.0.0-c2097c7c/mlkem/mlkem768.go	1980-01-10 00:00:00.000000000 +0100
@@ -177,10 +177,7 @@
 	var z [32]byte
 	drbg.Read(z[:])
 	kemKeyGen(dk, &d, &z)
-	if err := fips140.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)
-	}
+	fips140.PCT("ML-KEM PCT", func() error { return kemPCT(dk) })
 	fips140.RecordApproved()
 	return dk, nil
 }
@@ -208,10 +205,6 @@
 	d := (*[32]byte)(seed[:32])
 	z := (*[32]byte)(seed[32:])
 	kemKeyGen(dk, d, z)
-	if err := fips140.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)
-	}
 	fips140.RecordApproved()
 	return dk, nil
 }
diff -ru golang.org/fips140@v1.0.0/fips140/v1.0.0/rsa/keygen.go golang.org/fips140@v1.0.0-c2097c7c/fips140/v1.0.0-c2097c7c/rsa/keygen.go
--- golang.org/fips140@v1.0.0/fips140/v1.0.0/rsa/keygen.go	1980-01-10 00:00:00.000000000 +0100
+++ golang.org/fips140@v1.0.0-c2097c7c/fips140/v1.0.0-c2097c7c/rsa/keygen.go	1980-01-10 00:00:00.000000000 +0100
@@ -105,7 +105,28 @@
 		// negligible chance of failure we can defer the check to the end of key
 		// generation and return an error if it fails. See [checkPrivateKey].

-		return newPrivateKey(N, 65537, d, P, Q)
+		k, err := newPrivateKey(N, 65537, d, P, Q)
+		if err != nil {
+			return nil, err
+		}
+
+		if k.fipsApproved {
+			fips140.PCT("RSA sign and verify PCT", func() error {
+				hash := []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,
+				}
+				sig, err := signPKCS1v15(k, "SHA-256", hash)
+				if err != nil {
+					return err
+				}
+				return verifyPKCS1v15(k.PublicKey(), "SHA-256", hash, sig)
+			})
+		}
+
+		return k, nil
 	}
 }

diff -ru golang.org/fips140@v1.0.0/fips140/v1.0.0/rsa/rsa.go golang.org/fips140@v1.0.0-c2097c7c/fips140/v1.0.0-c2097c7c/rsa/rsa.go
--- golang.org/fips140@v1.0.0/fips140/v1.0.0/rsa/rsa.go	1980-01-10 00:00:00.000000000 +0100
+++ golang.org/fips140@v1.0.0-c2097c7c/fips140/v1.0.0-c2097c7c/rsa/rsa.go	1980-01-10 00:00:00.000000000 +0100
@@ -310,26 +310,6 @@
 		return errors.New("crypto/rsa: d too small")
 	}

-	// If the key is still in scope for FIPS mode, perform a Pairwise
-	// Consistency Test.
-	if priv.fipsApproved {
-		if err := fips140.PCT("RSA sign and verify PCT", func() error {
-			hash := []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,
-			}
-			sig, err := signPKCS1v15(priv, "SHA-256", hash)
-			if err != nil {
-				return err
-			}
-			return verifyPKCS1v15(priv.PublicKey(), "SHA-256", hash, sig)
-		}); err != nil {
-			return err
-		}
-	}
-
 	return nil
 }


Cq-Include-Trybots: luci.golang.try:gotip-linux-amd64-longtest
Change-Id: I6a6a6964b1780f19ec2b5202052de58b47d9342c
Reviewed-on: https://go-review.googlesource.com/c/go/+/701520
Reviewed-by: Junyang Shao <shaojunyang@google.com>
Auto-Submit: Filippo Valsorda <filippo@golang.org>
Commit-Queue: Junyang Shao <shaojunyang@google.com>
Reviewed-by: Roland Shoemaker <roland@golang.org>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
This commit is contained in:
Filippo Valsorda 2025-09-07 16:52:08 +02:00 committed by Gopher Robot
parent 9b7a328089
commit e8a4f508d1
8 changed files with 16 additions and 10 deletions

View file

@ -9,4 +9,4 @@
# #
# go test cmd/go/internal/fips140 -update # go test cmd/go/internal/fips140 -update
# #
v1.0.0.zip b50508feaeff05d22516b21e1fd210bbf5d6a1e422eaf2cfa23fe379342713b8 v1.0.0-c2097c7c.zip daf3614e0406f67ae6323c902db3f953a1effb199142362a039e7526dfb9368b

View file

@ -1 +1 @@
v1.0.0 v1.0.0-c2097c7c

1
lib/fips140/v1.0.0.txt Normal file
View file

@ -0,0 +1 @@
v1.0.0-c2097c7c

View file

@ -27,10 +27,10 @@ import (
"log" "log"
"os" "os"
"path/filepath" "path/filepath"
"regexp"
"strings" "strings"
"golang.org/x/mod/module" "golang.org/x/mod/module"
"golang.org/x/mod/semver"
modzip "golang.org/x/mod/zip" modzip "golang.org/x/mod/zip"
) )
@ -61,7 +61,7 @@ func main() {
// Must have valid version, and must not overwrite existing file. // Must have valid version, and must not overwrite existing file.
version := flag.Arg(0) version := flag.Arg(0)
if !regexp.MustCompile(`^v\d+\.\d+\.\d+$`).MatchString(version) { if semver.Canonical(version) != version {
log.Fatalf("invalid version %q; must be vX.Y.Z", version) log.Fatalf("invalid version %q; must be vX.Y.Z", version)
} }
if _, err := os.Stat(version + ".zip"); err == nil { if _, err := os.Stat(version + ".zip"); err == nil {
@ -117,7 +117,9 @@ func main() {
if !bytes.Contains(contents, []byte(returnLine)) { if !bytes.Contains(contents, []byte(returnLine)) {
log.Fatalf("did not find %q in fips140.go", returnLine) log.Fatalf("did not find %q in fips140.go", returnLine)
} }
newLine := `return "` + version + `"` // Use only the vX.Y.Z part of a possible vX.Y.Z-hash version.
v, _, _ := strings.Cut(version, "-")
newLine := `return "` + v + `"`
contents = bytes.ReplaceAll(contents, []byte(returnLine), []byte(newLine)) contents = bytes.ReplaceAll(contents, []byte(returnLine), []byte(newLine))
wf, err := zw.Create(f.Name) wf, err := zw.Create(f.Name)
if err != nil { if err != nil {

View file

@ -1,4 +1,4 @@
env snap=v1.0.0 env snap=v1.0.0-c2097c7c
env alias=inprocess env alias=inprocess
env GOFIPS140=$snap env GOFIPS140=$snap
@ -23,8 +23,7 @@ stdout crypto/internal/fips140/$snap/sha256
! stdout crypto/internal/fips140/check ! stdout crypto/internal/fips140/check
# again with GOFIPS140=$alias # again with GOFIPS140=$alias
# TODO: enable when we add inprocess.txt env GOFIPS140=$alias
# env GOFIPS140=$alias
# default GODEBUG includes fips140=on # default GODEBUG includes fips140=on
go list -f '{{.DefaultGODEBUG}}' go list -f '{{.DefaultGODEBUG}}'

View file

@ -74,6 +74,9 @@ func TestVersion(t *testing.T) {
continue continue
} }
exp := setting.Value exp := setting.Value
// Remove the -hash suffix, if any.
// The version from fips140.Version omits it.
exp, _, _ = strings.Cut(exp, "-")
if v := fips140.Version(); v != exp { if v := fips140.Version(); v != exp {
t.Errorf("Version is %q, expected %q", v, exp) t.Errorf("Version is %q, expected %q", v, exp)
} }

View file

@ -85,7 +85,7 @@ func gofips140() string {
} }
// isFIPSVersion reports whether v is a valid FIPS version, // isFIPSVersion reports whether v is a valid FIPS version,
// of the form vX.Y.Z. // of the form vX.Y.Z or vX.Y.Z-hash.
func isFIPSVersion(v string) bool { func isFIPSVersion(v string) bool {
if !strings.HasPrefix(v, "v") { if !strings.HasPrefix(v, "v") {
return false return false
@ -99,7 +99,8 @@ func isFIPSVersion(v string) bool {
return false return false
} }
v, ok = skipNum(v[len("."):]) v, ok = skipNum(v[len("."):])
return ok && v == "" hasHash := strings.HasPrefix(v, "-") && len(v) == len("-")+8
return ok && (v == "" || hasHash)
} }
// skipNum skips the leading text matching [0-9]+ // skipNum skips the leading text matching [0-9]+