Skip to content

Commit 53b8834

Browse files
committed
crypto/sha256,crypto/sha512: test fallback implementations
This will be required for golang#69536 but is also good hygiene and required by go.dev/wiki/AssemblyPolicy. > The code must be tested in our CI. This means there need to be > builders that support the instructions, and if there are multiple (or > fallback) paths they must be tested separately. The new crypto/internal/impl registry lets us select alternative implementations from both the same package and importers (such as crypto/sha256 tests once we have crypto/internal/fips/sha256, or crypto/hmac). Updates golang#69592 Updates golang#69593 Change-Id: Ifea22a9fc9ccffcaf4924ff6bd08da7c9bd39e99 Cq-Include-Trybots: luci.golang.try:gotip-linux-arm64-longtest,gotip-linux-amd64-longtest,gotip-linux-ppc64le_power8,gotip-linux-ppc64_power8
1 parent 62cfb98 commit 53b8834

13 files changed

+276
-63
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// Copyright 2024 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package cryptotest
6+
7+
import (
8+
"crypto/internal/boring"
9+
"crypto/internal/impl"
10+
"internal/testenv"
11+
"testing"
12+
)
13+
14+
// TestAllImplementations runs the provided test function with each available
15+
// implementation of the package registered with crypto/internal/impl. If there
16+
// are no alternative implementations for pkg, f is invoked directly once.
17+
func TestAllImplementations(t *testing.T, pkg string, f func(t *testing.T)) {
18+
// BoringCrypto bypasses the multiple Go implementations.
19+
if boring.Enabled {
20+
f(t)
21+
return
22+
}
23+
24+
impls := impl.List(pkg)
25+
if len(impls) == 0 {
26+
f(t)
27+
return
28+
}
29+
30+
t.Cleanup(func() { impl.Reset(pkg) })
31+
32+
for _, name := range impls {
33+
if available := impl.Select(pkg, name); available {
34+
t.Run(name, f)
35+
} else {
36+
t.Run(name, func(t *testing.T) {
37+
if testenv.Builder() != "" {
38+
if name == "SHA-NI" {
39+
t.Skip("known issue, see golang.org/issue/69592")
40+
}
41+
if name == "Armv8.2" {
42+
t.Skip("known issue, see golang.org/issue/69593")
43+
}
44+
t.Error("builder doesn't support CPU features needed to test this implementation")
45+
} else {
46+
t.Skip("implementation not supported")
47+
}
48+
})
49+
}
50+
51+
}
52+
53+
// Test the generic implementation.
54+
impl.Select(pkg, "")
55+
t.Run("Base", f)
56+
}

src/crypto/internal/impl/impl.go

+88
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
// Copyright 2024 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
// Package impl is a registry of alternative implementations of cryptographic
6+
// primitives, to allow selecting them for testing.
7+
package impl
8+
9+
type implementation struct {
10+
Package string
11+
Name string
12+
Available bool
13+
Toggle *bool
14+
}
15+
16+
var allImplementations []implementation
17+
18+
// Register records an alternative implementation of a cryptographic primitive.
19+
// The implementation might be available or not based on CPU support. If
20+
// available is false, the implementation is unavailable and can't be tested on
21+
// this machine. If available is true, it can be set to false to disable the
22+
// implementation. If all alternative implementations but one are disabled, the
23+
// remaining one must be used (i.e. disabling one implementation must not
24+
// implicitly disable any other). Each package has an implicit base
25+
// implementation that is selected when all alternatives are unavailable or
26+
// disabled.
27+
func Register(pkg, name string, available *bool) {
28+
allImplementations = append(allImplementations, implementation{
29+
Package: pkg,
30+
Name: name,
31+
Available: *available,
32+
Toggle: available,
33+
})
34+
}
35+
36+
// List returns the names of all alternative implementations registered for the
37+
// given package, whether available or not. The implicit base implementation is
38+
// not included.
39+
func List(pkg string) []string {
40+
var names []string
41+
for _, i := range allImplementations {
42+
if i.Package == pkg {
43+
names = append(names, i.Name)
44+
}
45+
}
46+
return names
47+
}
48+
49+
func available(pkg, name string) bool {
50+
for _, i := range allImplementations {
51+
if i.Package == pkg && i.Name == name {
52+
return i.Available
53+
}
54+
}
55+
panic("unknown implementation")
56+
}
57+
58+
// Select disables all implementations for the given package except the one
59+
// with the given name. If name is empty, the base implementation is selected.
60+
// It returns whether the selected implementation is available.
61+
func Select(pkg, name string) bool {
62+
if name == "" {
63+
for _, i := range allImplementations {
64+
if i.Package == pkg {
65+
*i.Toggle = false
66+
}
67+
}
68+
return true
69+
}
70+
if !available(pkg, name) {
71+
return false
72+
}
73+
for _, i := range allImplementations {
74+
if i.Package == pkg {
75+
*i.Toggle = i.Name == name
76+
}
77+
}
78+
return true
79+
}
80+
81+
func Reset(pkg string) {
82+
for _, i := range allImplementations {
83+
if i.Package == pkg {
84+
*i.Toggle = i.Available
85+
return
86+
}
87+
}
88+
}

src/crypto/sha256/sha256_test.go

+17-23
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import (
1010
"bytes"
1111
"crypto/internal/boring"
1212
"crypto/internal/cryptotest"
13-
"crypto/rand"
1413
"encoding"
1514
"fmt"
1615
"hash"
@@ -93,8 +92,11 @@ var golden224 = []sha256Test{
9392
}
9493

9594
func TestGolden(t *testing.T) {
96-
for i := 0; i < len(golden); i++ {
97-
g := golden[i]
95+
cryptotest.TestAllImplementations(t, "crypto/sha256", testGolden)
96+
}
97+
98+
func testGolden(t *testing.T) {
99+
for _, g := range golden {
98100
s := fmt.Sprintf("%x", Sum256([]byte(g.in)))
99101
if s != g.out {
100102
t.Fatalf("Sum256 function: sha256(%s) = %s want %s", g.in, s, g.out)
@@ -115,8 +117,7 @@ func TestGolden(t *testing.T) {
115117
c.Reset()
116118
}
117119
}
118-
for i := 0; i < len(golden224); i++ {
119-
g := golden224[i]
120+
for _, g := range golden224 {
120121
s := fmt.Sprintf("%x", Sum224([]byte(g.in)))
121122
if s != g.out {
122123
t.Fatalf("Sum224 function: sha224(%s) = %s want %s", g.in, s, g.out)
@@ -140,6 +141,10 @@ func TestGolden(t *testing.T) {
140141
}
141142

142143
func TestGoldenMarshal(t *testing.T) {
144+
cryptotest.TestAllImplementations(t, "crypto/sha256", testGoldenMarshal)
145+
}
146+
147+
func testGoldenMarshal(t *testing.T) {
143148
tests := []struct {
144149
name string
145150
newHash func() hash.Hash
@@ -228,21 +233,6 @@ func TestBlockSize(t *testing.T) {
228233
}
229234
}
230235

231-
// Tests that blockGeneric (pure Go) and block (in assembly for some architectures) match.
232-
func TestBlockGeneric(t *testing.T) {
233-
if boring.Enabled {
234-
t.Skip("BoringCrypto doesn't expose digest")
235-
}
236-
gen, asm := New().(*digest), New().(*digest)
237-
buf := make([]byte, BlockSize*20) // arbitrary factor
238-
rand.Read(buf)
239-
blockGeneric(gen, buf)
240-
block(asm, buf)
241-
if *gen != *asm {
242-
t.Error("block and blockGeneric resulted in different states")
243-
}
244-
}
245-
246236
// Tests for unmarshaling hashes that have hashed a large amount of data
247237
// The initial hash generation is omitted from the test, because it takes a long time.
248238
// The test contains some already-generated states, and their expected sums
@@ -338,12 +328,16 @@ func TestCgo(t *testing.T) {
338328
h.Sum(nil)
339329
}
340330

341-
func TestSHA256Hash(t *testing.T) {
331+
func TestHash(t *testing.T) {
342332
t.Run("SHA-224", func(t *testing.T) {
343-
cryptotest.TestHash(t, New224)
333+
cryptotest.TestAllImplementations(t, "crypto/sha256", func(t *testing.T) {
334+
cryptotest.TestHash(t, New224)
335+
})
344336
})
345337
t.Run("SHA-256", func(t *testing.T) {
346-
cryptotest.TestHash(t, New)
338+
cryptotest.TestAllImplementations(t, "crypto/sha256", func(t *testing.T) {
339+
cryptotest.TestHash(t, New)
340+
})
347341
})
348342
}
349343

src/crypto/sha256/sha256block_amd64.go

+12-5
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,25 @@
66

77
package sha256
88

9-
import "internal/cpu"
9+
import (
10+
"crypto/internal/impl"
11+
"internal/cpu"
12+
)
13+
14+
var useAVX2 = cpu.X86.HasAVX2 && cpu.X86.HasBMI2
15+
var useSHANI = useAVX2 && cpu.X86.HasSHA
16+
17+
func init() {
18+
impl.Register("crypto/sha256", "AVX2", &useAVX2)
19+
impl.Register("crypto/sha256", "SHA-NI", &useSHANI)
20+
}
1021

1122
//go:noescape
1223
func blockAMD64(dig *digest, p []byte)
1324

14-
var useAVX2 = cpu.X86.HasAVX2 && cpu.X86.HasBMI2
15-
1625
//go:noescape
1726
func blockAVX2(dig *digest, p []byte)
1827

19-
var useSHANI = useAVX2 && cpu.X86.HasSHA
20-
2128
//go:noescape
2229
func blockSHANI(dig *digest, p []byte)
2330

src/crypto/sha256/sha256block_arm64.go

+11-2
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,22 @@
66

77
package sha256
88

9-
import "internal/cpu"
9+
import (
10+
"crypto/internal/impl"
11+
"internal/cpu"
12+
)
13+
14+
var useSHA2 = cpu.ARM64.HasSHA2
15+
16+
func init() {
17+
impl.Register("crypto/sha256", "Armv8.0", &useSHA2)
18+
}
1019

1120
//go:noescape
1221
func blockSHA2(dig *digest, p []byte)
1322

1423
func block(dig *digest, p []byte) {
15-
if cpu.ARM64.HasSHA2 {
24+
if useSHA2 {
1625
blockSHA2(dig, p)
1726
} else {
1827
blockGeneric(dig, p)

src/crypto/sha256/sha256block_ppc64x.go

+8-1
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,21 @@
66

77
package sha256
88

9-
import "internal/godebug"
9+
import (
10+
"crypto/internal/impl"
11+
"internal/godebug"
12+
)
1013

1114
// The POWER architecture doesn't have a way to turn off SHA-2 support at
1215
// runtime with GODEBUG=cpu.something=off, so introduce a new GODEBUG knob for
1316
// that. It's intentionally only checked at init() time, to avoid the
1417
// performance overhead of checking it on every block.
1518
var ppc64sha2 = godebug.New("#ppc64sha2").Value() != "off"
1619

20+
func init() {
21+
impl.Register("crypto/sha256", "POWER8", &ppc64sha2)
22+
}
23+
1724
//go:noescape
1825
func blockPOWER(dig *digest, p []byte)
1926

src/crypto/sha256/sha256block_s390x.go

+13-2
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,24 @@
66

77
package sha256
88

9-
import "internal/cpu"
9+
import (
10+
"crypto/internal/impl"
11+
"internal/cpu"
12+
)
13+
14+
var useSHA256 = cpu.S390X.HasSHA256
15+
16+
func init() {
17+
// CP Assist for Cryptographic Functions (CPACF)
18+
// https://www.ibm.com/docs/en/zos/3.1.0?topic=icsf-cp-assist-cryptographic-functions-cpacf
19+
impl.Register("crypto/sha256", "CPACF", &useSHA256)
20+
}
1021

1122
//go:noescape
1223
func blockS390X(dig *digest, p []byte)
1324

1425
func block(dig *digest, p []byte) {
15-
if cpu.S390X.HasSHA256 {
26+
if useSHA256 {
1627
blockS390X(dig, p)
1728
} else {
1829
blockGeneric(dig, p)

0 commit comments

Comments
 (0)