From be84aeed7d21f5e5be37dee3c827175acebb6dda Mon Sep 17 00:00:00 2001 From: Adam Langley Date: Tue, 5 Sep 2023 20:53:37 +0000 Subject: [PATCH] acvptool: create fresh variables in loops. Referencing a variable in a closure captures it by _address_. So referencing a loop variable can go horribly wrong: https://go.dev/play/p/f2ivPAIN_bG This is accepted as essentially a bug by Go and will be fixed in a future release (https://github.com/golang/go/wiki/LoopvarExperiment). But, for now at least, work around it. Our tests trim the ACVP inputs to only have a single test case per group in many cases, which hides most of this issue from tests. When we run run full ACVP sets, our modulewrapper is seemingly fast enough not to notice there either. But I've updated one of the tests here by duplicating a test case enough that it catches this a meaningful amount of the time. Change-Id: I8216c00f67636ab7dad927eae4b49ae45ae3cf31 Bug: 646 Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/62965 Reviewed-by: David Benjamin Auto-Submit: Adam Langley Commit-Queue: David Benjamin --- .../fipstools/acvp/acvptool/subprocess/aead.go | 3 +++ .../acvp/acvptool/subprocess/block.go | 3 +++ .../fipstools/acvp/acvptool/subprocess/drbg.go | 3 +++ .../acvp/acvptool/subprocess/ecdsa.go | 4 ++++ .../fipstools/acvp/acvptool/subprocess/hash.go | 3 +++ .../fipstools/acvp/acvptool/subprocess/hkdf.go | 2 ++ .../fipstools/acvp/acvptool/subprocess/hmac.go | 3 +++ util/fipstools/acvp/acvptool/subprocess/kas.go | 3 +++ .../acvp/acvptool/subprocess/kasdh.go | 3 +++ util/fipstools/acvp/acvptool/subprocess/kdf.go | 2 ++ .../acvp/acvptool/subprocess/keyedMac.go | 2 ++ util/fipstools/acvp/acvptool/subprocess/rsa.go | 11 +++++++++++ .../acvp/acvptool/subprocess/tls13.go | 2 ++ .../acvp/acvptool/subprocess/tlskdf.go | 2 ++ util/fipstools/acvp/acvptool/subprocess/xts.go | 2 ++ .../acvp/acvptool/test/expected/TLS12.bz2 | Bin 934 -> 1333 bytes .../acvp/acvptool/test/vectors/TLS12.bz2 | Bin 999 -> 1356 bytes 17 files changed, 48 insertions(+) diff --git a/util/fipstools/acvp/acvptool/subprocess/aead.go b/util/fipstools/acvp/acvptool/subprocess/aead.go index ba0eee966..c38b17073 100644 --- a/util/fipstools/acvp/acvptool/subprocess/aead.go +++ b/util/fipstools/acvp/acvptool/subprocess/aead.go @@ -72,6 +72,7 @@ func (a *aead) Process(vectorSet []byte, m Transactable) (any, error) { // versions of the ACVP documents. You can find fragments in // https://github.com/usnistgov/ACVP.) for _, group := range parsed.Groups { + group := group response := aeadTestGroupResponse{ ID: group.ID, } @@ -102,6 +103,8 @@ func (a *aead) Process(vectorSet []byte, m Transactable) (any, error) { tagBytes := group.TagBits / 8 for _, test := range group.Tests { + test := test + if len(test.KeyHex) != keyBytes*2 { return nil, fmt.Errorf("test case %d/%d contains key %q of length %d, but expected %d-bit key", group.ID, test.ID, test.KeyHex, len(test.KeyHex), group.KeyBits) } diff --git a/util/fipstools/acvp/acvptool/subprocess/block.go b/util/fipstools/acvp/acvptool/subprocess/block.go index 2f058027e..bcc6613a2 100644 --- a/util/fipstools/acvp/acvptool/subprocess/block.go +++ b/util/fipstools/acvp/acvptool/subprocess/block.go @@ -299,6 +299,7 @@ func (b *blockCipher) Process(vectorSet []byte, m Transactable) (any, error) { // http://usnistgov.github.io/ACVP/artifacts/draft-celi-acvp-block-ciph-00.html#rfc.section.5.2 // for details about the tests. for _, group := range parsed.Groups { + group := group response := blockCipherTestGroupResponse{ ID: group.ID, } @@ -346,6 +347,8 @@ func (b *blockCipher) Process(vectorSet []byte, m Transactable) (any, error) { } for _, test := range group.Tests { + test := test + if len(test.KeyHex) == 0 && len(test.Key1Hex) > 0 { // 3DES encodes the key differently. test.KeyHex = test.Key1Hex + test.Key2Hex + test.Key3Hex diff --git a/util/fipstools/acvp/acvptool/subprocess/drbg.go b/util/fipstools/acvp/acvptool/subprocess/drbg.go index b403f0461..87584d63d 100644 --- a/util/fipstools/acvp/acvptool/subprocess/drbg.go +++ b/util/fipstools/acvp/acvptool/subprocess/drbg.go @@ -84,6 +84,7 @@ func (d *drbg) Process(vectorSet []byte, m Transactable) (any, error) { // https://pages.nist.gov/ACVP/draft-vassilev-acvp-drbg.html#name-test-vectors // for details about the tests. for _, group := range parsed.Groups { + group := group response := drbgTestGroupResponse{ ID: group.ID, } @@ -97,6 +98,8 @@ func (d *drbg) Process(vectorSet []byte, m Transactable) (any, error) { } for _, test := range group.Tests { + test := test + ent, err := extractField(test.EntropyHex, group.EntropyBits) if err != nil { return nil, fmt.Errorf("failed to extract entropy hex from test case %d/%d: %s", group.ID, test.ID, err) diff --git a/util/fipstools/acvp/acvptool/subprocess/ecdsa.go b/util/fipstools/acvp/acvptool/subprocess/ecdsa.go index 16d3a8335..69706bdda 100644 --- a/util/fipstools/acvp/acvptool/subprocess/ecdsa.go +++ b/util/fipstools/acvp/acvptool/subprocess/ecdsa.go @@ -83,6 +83,8 @@ func (e *ecdsa) Process(vectorSet []byte, m Transactable) (any, error) { // https://pages.nist.gov/ACVP/draft-fussell-acvp-ecdsa.html#name-test-vectors // for details about the tests. for _, group := range parsed.Groups { + group := group + if _, ok := e.curves[group.Curve]; !ok { return nil, fmt.Errorf("curve %q in test group %d not supported", group.Curve, group.ID) } @@ -93,6 +95,8 @@ func (e *ecdsa) Process(vectorSet []byte, m Transactable) (any, error) { var sigGenPrivateKey []byte for _, test := range group.Tests { + test := test + var testResp ecdsaTestResponse testResp.ID = test.ID diff --git a/util/fipstools/acvp/acvptool/subprocess/hash.go b/util/fipstools/acvp/acvptool/subprocess/hash.go index 1f34d1a90..aeac6d669 100644 --- a/util/fipstools/acvp/acvptool/subprocess/hash.go +++ b/util/fipstools/acvp/acvptool/subprocess/hash.go @@ -73,11 +73,14 @@ func (h *hashPrimitive) Process(vectorSet []byte, m Transactable) (any, error) { // https://pages.nist.gov/ACVP/draft-celi-acvp-sha.html#name-test-vectors // for details about the tests. for _, group := range parsed.Groups { + group := group response := hashTestGroupResponse{ ID: group.ID, } for _, test := range group.Tests { + test := test + if uint64(len(test.MsgHex))*4 != test.BitLength { return nil, fmt.Errorf("test case %d/%d contains hex message of length %d but specifies a bit length of %d", group.ID, test.ID, len(test.MsgHex), test.BitLength) } diff --git a/util/fipstools/acvp/acvptool/subprocess/hkdf.go b/util/fipstools/acvp/acvptool/subprocess/hkdf.go index 3a6ba04c6..c64e2b862 100644 --- a/util/fipstools/acvp/acvptool/subprocess/hkdf.go +++ b/util/fipstools/acvp/acvptool/subprocess/hkdf.go @@ -124,6 +124,7 @@ func (k *hkdf) Process(vectorSet []byte, m Transactable) (any, error) { var respGroups []hkdfTestGroupResponse for _, group := range parsed.Groups { + group := group groupResp := hkdfTestGroupResponse{ID: group.ID} var isValidationTest bool @@ -142,6 +143,7 @@ func (k *hkdf) Process(vectorSet []byte, m Transactable) (any, error) { } for _, test := range group.Tests { + test := test testResp := hkdfTestResponse{ID: test.ID} key, salt, err := test.Params.extract() diff --git a/util/fipstools/acvp/acvptool/subprocess/hmac.go b/util/fipstools/acvp/acvptool/subprocess/hmac.go index 8fc76951c..6b8a3cfae 100644 --- a/util/fipstools/acvp/acvptool/subprocess/hmac.go +++ b/util/fipstools/acvp/acvptool/subprocess/hmac.go @@ -87,6 +87,7 @@ func (h *hmacPrimitive) Process(vectorSet []byte, m Transactable) (any, error) { // https://pages.nist.gov/ACVP/draft-fussell-acvp-mac.html#name-test-vectors // for details about the tests. for _, group := range parsed.Groups { + group := group response := hmacTestGroupResponse{ ID: group.ID, } @@ -99,6 +100,8 @@ func (h *hmacPrimitive) Process(vectorSet []byte, m Transactable) (any, error) { outBytes := group.MACBits / 8 for _, test := range group.Tests { + test := test + if len(test.MsgHex)*4 != group.MsgBits { return nil, fmt.Errorf("test case %d/%d contains hex message of length %d but specifies a bit length of %d", group.ID, test.ID, len(test.MsgHex), group.MsgBits) } diff --git a/util/fipstools/acvp/acvptool/subprocess/kas.go b/util/fipstools/acvp/acvptool/subprocess/kas.go index cbc99ed58..4c99f8aa3 100644 --- a/util/fipstools/acvp/acvptool/subprocess/kas.go +++ b/util/fipstools/acvp/acvptool/subprocess/kas.go @@ -77,6 +77,7 @@ func (k *kas) Process(vectorSet []byte, m Transactable) (any, error) { // See https://pages.nist.gov/ACVP/draft-fussell-acvp-kas-ecc.html#name-test-vectors var ret []kasTestGroupResponse for _, group := range parsed.Groups { + group := group response := kasTestGroupResponse{ ID: group.ID, } @@ -119,6 +120,8 @@ func (k *kas) Process(vectorSet []byte, m Transactable) (any, error) { method := "ECDH/" + group.Curve for _, test := range group.Tests { + test := test + var xHex, yHex, privateKeyHex string if useStaticNamedFields { xHex, yHex, privateKeyHex = test.StaticXHex, test.StaticYHex, test.StaticPrivateKeyHex diff --git a/util/fipstools/acvp/acvptool/subprocess/kasdh.go b/util/fipstools/acvp/acvptool/subprocess/kasdh.go index f262b8200..212dd3165 100644 --- a/util/fipstools/acvp/acvptool/subprocess/kasdh.go +++ b/util/fipstools/acvp/acvptool/subprocess/kasdh.go @@ -68,6 +68,7 @@ func (k *kasDH) Process(vectorSet []byte, m Transactable) (any, error) { // See https://pages.nist.gov/ACVP/draft-hammett-acvp-kas-ffc-sp800-56ar3.html var ret []kasDHTestGroupResponse for _, group := range parsed.Groups { + group := group response := kasDHTestGroupResponse{ ID: group.ID, } @@ -110,6 +111,8 @@ func (k *kasDH) Process(vectorSet []byte, m Transactable) (any, error) { const method = "FFDH" for _, test := range group.Tests { + test := test + if len(test.PeerPublicHex) == 0 { return nil, fmt.Errorf("%d/%d is missing peer's key", group.ID, test.ID) } diff --git a/util/fipstools/acvp/acvptool/subprocess/kdf.go b/util/fipstools/acvp/acvptool/subprocess/kdf.go index e27fcaa9a..6e4145890 100644 --- a/util/fipstools/acvp/acvptool/subprocess/kdf.go +++ b/util/fipstools/acvp/acvptool/subprocess/kdf.go @@ -68,6 +68,7 @@ func (k *kdfPrimitive) Process(vectorSet []byte, m Transactable) (any, error) { var respGroups []kdfTestGroupResponse for _, group := range parsed.Groups { + group := group groupResp := kdfTestGroupResponse{ID: group.ID} if group.OutputBits%8 != 0 { @@ -91,6 +92,7 @@ func (k *kdfPrimitive) Process(vectorSet []byte, m Transactable) (any, error) { outputBytes := uint32le(group.OutputBits / 8) for _, test := range group.Tests { + test := test testResp := kdfTestResponse{ID: test.ID} var key []byte diff --git a/util/fipstools/acvp/acvptool/subprocess/keyedMac.go b/util/fipstools/acvp/acvptool/subprocess/keyedMac.go index e43ab5d53..c91bb4160 100644 --- a/util/fipstools/acvp/acvptool/subprocess/keyedMac.go +++ b/util/fipstools/acvp/acvptool/subprocess/keyedMac.go @@ -65,6 +65,7 @@ func (k *keyedMACPrimitive) Process(vectorSet []byte, m Transactable) (any, erro var respGroups []keyedMACTestGroupResponse for _, group := range vs.Groups { + group := group respGroup := keyedMACTestGroupResponse{ID: group.ID} if group.KeyBits%8 != 0 { @@ -90,6 +91,7 @@ func (k *keyedMACPrimitive) Process(vectorSet []byte, m Transactable) (any, erro outputBytes := uint32le(group.MACBits / 8) for _, test := range group.Tests { + test := test respTest := keyedMACTestResponse{ID: test.ID} // Validate input. diff --git a/util/fipstools/acvp/acvptool/subprocess/rsa.go b/util/fipstools/acvp/acvptool/subprocess/rsa.go index d975026e6..923cdad0a 100644 --- a/util/fipstools/acvp/acvptool/subprocess/rsa.go +++ b/util/fipstools/acvp/acvptool/subprocess/rsa.go @@ -126,6 +126,8 @@ func processKeyGen(vectorSet []byte, m Transactable) (any, error) { var ret []rsaKeyGenTestGroupResponse for _, group := range parsed.Groups { + group := group + // GDT means "Generated data test", i.e. "please generate an RSA key". const expectedType = "GDT" if group.Type != expectedType { @@ -137,6 +139,8 @@ func processKeyGen(vectorSet []byte, m Transactable) (any, error) { } for _, test := range group.Tests { + test := test + m.TransactAsync("RSA/keyGen", 5, [][]byte{uint32le(group.ModulusBits)}, func(result [][]byte) error { response.Tests = append(response.Tests, rsaKeyGenTestResponse{ ID: test.ID, @@ -171,6 +175,8 @@ func processSigGen(vectorSet []byte, m Transactable) (any, error) { var ret []rsaSigGenTestGroupResponse for _, group := range parsed.Groups { + group := group + // GDT means "Generated data test", i.e. "please generate an RSA signature". const expectedType = "GDT" if group.Type != expectedType { @@ -184,6 +190,8 @@ func processSigGen(vectorSet []byte, m Transactable) (any, error) { operation := "RSA/sigGen/" + group.Hash + "/" + group.SigType for _, test := range group.Tests { + test := test + msg, err := hex.DecodeString(test.MessageHex) if err != nil { return nil, fmt.Errorf("test case %d/%d contains invalid hex: %s", group.ID, test.ID, err) @@ -226,6 +234,8 @@ func processSigVer(vectorSet []byte, m Transactable) (any, error) { var ret []rsaSigVerTestGroupResponse for _, group := range parsed.Groups { + group := group + // GDT means "Generated data test", which makes no sense in this context. const expectedType = "GDT" if group.Type != expectedType { @@ -248,6 +258,7 @@ func processSigVer(vectorSet []byte, m Transactable) (any, error) { operation := "RSA/sigVer/" + group.Hash + "/" + group.SigType for _, test := range group.Tests { + test := test msg, err := hex.DecodeString(test.MessageHex) if err != nil { return nil, fmt.Errorf("test case %d/%d contains invalid hex: %s", group.ID, test.ID, err) diff --git a/util/fipstools/acvp/acvptool/subprocess/tls13.go b/util/fipstools/acvp/acvptool/subprocess/tls13.go index af2aae835..bd121422e 100644 --- a/util/fipstools/acvp/acvptool/subprocess/tls13.go +++ b/util/fipstools/acvp/acvptool/subprocess/tls13.go @@ -77,9 +77,11 @@ func (k *tls13) Process(vectorSet []byte, m Transactable) (any, error) { var respGroups []tls13TestGroupResponse for _, group := range parsed.Groups { + group := group groupResp := tls13TestGroupResponse{ID: group.ID} for _, test := range group.Tests { + test := test testResp := tls13TestResponse{ID: test.ID} clientHello, err := hex.DecodeString(test.ClientHelloHex) diff --git a/util/fipstools/acvp/acvptool/subprocess/tlskdf.go b/util/fipstools/acvp/acvptool/subprocess/tlskdf.go index 3a0d7ceaf..251b53e6d 100644 --- a/util/fipstools/acvp/acvptool/subprocess/tlskdf.go +++ b/util/fipstools/acvp/acvptool/subprocess/tlskdf.go @@ -64,6 +64,7 @@ func (k *tlsKDF) Process(vectorSet []byte, m Transactable) (any, error) { // See https://pages.nist.gov/ACVP/draft-celi-acvp-kdf-tls.html var ret []tlsKDFTestGroupResponse for _, group := range parsed.Groups { + group := group response := tlsKDFTestGroupResponse{ ID: group.ID, } @@ -82,6 +83,7 @@ func (k *tlsKDF) Process(vectorSet []byte, m Transactable) (any, error) { method := "TLSKDF/1.2/" + group.Hash for _, test := range group.Tests { + test := test pms, err := hex.DecodeString(test.PMSHex) if err != nil { return nil, err diff --git a/util/fipstools/acvp/acvptool/subprocess/xts.go b/util/fipstools/acvp/acvptool/subprocess/xts.go index e81340973..5a9e7402a 100644 --- a/util/fipstools/acvp/acvptool/subprocess/xts.go +++ b/util/fipstools/acvp/acvptool/subprocess/xts.go @@ -67,6 +67,7 @@ func (h *xts) Process(vectorSet []byte, m Transactable) (any, error) { var ret []xtsTestGroupResponse for _, group := range parsed.Groups { + group := group response := xtsTestGroupResponse{ ID: group.ID, } @@ -88,6 +89,7 @@ func (h *xts) Process(vectorSet []byte, m Transactable) (any, error) { funcName := "AES-XTS/" + group.Direction for _, test := range group.Tests { + test := test if group.KeyLen != len(test.KeyHex)*4/2 { return nil, fmt.Errorf("test case %d/%d contains hex message of length %d but specifies a key length of %d (remember that XTS keys are twice the length of the underlying key size)", group.ID, test.ID, len(test.KeyHex), group.KeyLen) } diff --git a/util/fipstools/acvp/acvptool/test/expected/TLS12.bz2 b/util/fipstools/acvp/acvptool/test/expected/TLS12.bz2 index d83b691619b9a4b97bd250490fa5642084680ae9..ff4ded067d9056f039bfd435c75b587bcee56433 100644 GIT binary patch literal 1333 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 literal 999 zcmVzxmNqN4Jz(@ia!I2Q9DH~Z}nL~f%q5k6ime7|UU|(-6#0hsMYKEy*~=12rgvi8+fg;)mv$^Zh zy&adHLUMWCs37a=bL$R6y>}8mXH3RZ<-w0HC~s)B;6-^_ye1w&OGu%;>&%}e2z_3= z+#EF`zY^BRLdm+r%{QV*+bNWeWWpe48<51V!X-5t)YZ63)f6P?Lm*3H6=s zlYZw(i$g>`9TJhO$jgRaGo>3b3@?u@UP?FNhnG9jS(8UOKrB6x!3?I_JHfUCF7Plt zEtattsNP#=Qo;zGFiYcS;!r0__gO%w&SJ`pCj?B$W1|Ma%Pc~K&dk@6#L=fGe7jc! z^m)S*L!kyo5;G{!+Iwx0;Pfmg%+>{}OrJ8U!_Xebb5p(sQWiq>7Yh$JNJo2xnI{cY z7GIOPfSsKx=4~ahpu%M#%(Ym6JVw!8Yf5#o(4tWwabG~}`4KnYHex~0bR6O5BrU(g zu46XVWsA2toCW3(`HUK71SE@-LOE9K*?a5e>DkLvNhtWKx&_MMFUJ>~G>+&PgIA2z z1%%7Eg07)$mGtUA_d|$^Gc_uxCrG6YlrD zw?&jA#A@IVql=^Q#?DrnLw}txYNNi)NfMzbV~-t^Mzd#U-84zQo~|my?A`WgD2t;6 z-tf9dHJi=$oRg^5V(6_KXN679#_4kTToUfJ&7bVavK0w+v)40V?dx{CeEVdRo3DG3 z`3+}*5g*z&)M`PU>9V0ciQ%>*y|=nC>K%^9%y}LS&LDal*1o~<{5!mO^p0`xJ;TM9 z0r!oBp=ik+P3>6BHnbRVW~@+YlJA2?d3Cxhd)qV*Mi<>sfzmQV%JcJ$!-3hC1M=O- zJsD8sF&yJ%EaS&EM+@j0OxR@{Ms%g>P+(>oSxK>xjYJ`!Zk!IwTe-2fcc_@FW