1
2
3
4
5
6
7 package fipstest
8
9 import (
10 "bytes"
11 "crypto/internal/cryptotest"
12 "crypto/internal/fips140/drbg"
13 "crypto/internal/fips140/entropy"
14 "crypto/sha256"
15 "crypto/sha512"
16 "encoding/hex"
17 "flag"
18 "fmt"
19 "internal/testenv"
20 "io/fs"
21 "os"
22 "path/filepath"
23 "runtime"
24 "strings"
25 "testing"
26 "time"
27 )
28
29 var flagEntropySamples = flag.String("entropy-samples", "", "store entropy samples with the provided `suffix`")
30 var flagNISTSP80090B = flag.Bool("nist-sp800-90b", false, "run NIST SP 800-90B tests (requires docker)")
31
32 func TestEntropySamples(t *testing.T) {
33 cryptotest.MustSupportFIPS140(t)
34 now := time.Now().UTC()
35
36 var seqSamples [1_000_000]uint8
37 samplesOrTryAgain(t, seqSamples[:])
38 seqSamplesName := fmt.Sprintf("entropy_samples_sequential_%s_%s_%s_%s_%s.bin", entropy.Version(),
39 runtime.GOOS, runtime.GOARCH, *flagEntropySamples, now.Format("20060102T150405Z"))
40 if *flagEntropySamples != "" {
41 if err := os.WriteFile(seqSamplesName, seqSamples[:], 0644); err != nil {
42 t.Fatalf("failed to write samples to %q: %v", seqSamplesName, err)
43 }
44 t.Logf("wrote %s", seqSamplesName)
45 }
46
47 var restartSamples [1000][1000]uint8
48 for i := range restartSamples {
49 var samples [1024]uint8
50 samplesOrTryAgain(t, samples[:])
51 copy(restartSamples[i][:], samples[:])
52 }
53 restartSamplesName := fmt.Sprintf("entropy_samples_restart_%s_%s_%s_%s_%s.bin", entropy.Version(),
54 runtime.GOOS, runtime.GOARCH, *flagEntropySamples, now.Format("20060102T150405Z"))
55 if *flagEntropySamples != "" {
56 f, err := os.Create(restartSamplesName)
57 if err != nil {
58 t.Fatalf("failed to create %q: %v", restartSamplesName, err)
59 }
60 for i := range restartSamples {
61 if _, err := f.Write(restartSamples[i][:]); err != nil {
62 t.Fatalf("failed to write samples to %q: %v", restartSamplesName, err)
63 }
64 }
65 if err := f.Close(); err != nil {
66 t.Fatalf("failed to close %q: %v", restartSamplesName, err)
67 }
68 t.Logf("wrote %s", restartSamplesName)
69 }
70
71 if *flagNISTSP80090B {
72 if *flagEntropySamples == "" {
73 t.Fatalf("-nist-sp800-90b requires -entropy-samples to be set too")
74 }
75
76
77
78 if err := testenv.Command(t,
79 "docker", "image", "inspect", "nist-sp800-90b",
80 ).Run(); err != nil {
81 t.Logf("building nist-sp800-90b docker image")
82 dockerfile := filepath.Join(t.TempDir(), "Dockerfile.SP800-90B_EntropyAssessment")
83 if err := os.WriteFile(dockerfile, []byte(NISTSP80090BDockerfile), 0644); err != nil {
84 t.Fatalf("failed to write Dockerfile: %v", err)
85 }
86 out, err := testenv.Command(t,
87 "docker", "build", "-t", "nist-sp800-90b", "-f", dockerfile, "/var/empty",
88 ).CombinedOutput()
89 if err != nil {
90 t.Fatalf("failed to build nist-sp800-90b docker image: %v\n%s", err, out)
91 }
92 }
93
94 pwd, err := os.Getwd()
95 if err != nil {
96 t.Fatalf("failed to get current working directory: %v", err)
97 }
98 t.Logf("running ea_non_iid analysis")
99 out, err := testenv.Command(t,
100 "docker", "run", "--rm", "-v", fmt.Sprintf("%s:%s", pwd, pwd), "-w", pwd,
101 "nist-sp800-90b", "ea_non_iid", seqSamplesName, "8",
102 ).CombinedOutput()
103 if err != nil {
104 t.Fatalf("ea_non_iid failed: %v\n%s", err, out)
105 }
106 t.Logf("\n%s", out)
107
108 H_I := string(out)
109 H_I = strings.TrimSpace(H_I[strings.LastIndexByte(H_I, ' ')+1:])
110 t.Logf("running ea_restart analysis with H_I = %s", H_I)
111 out, err = testenv.Command(t,
112 "docker", "run", "--rm", "-v", fmt.Sprintf("%s:%s", pwd, pwd), "-w", pwd,
113 "nist-sp800-90b", "ea_restart", restartSamplesName, "8", H_I,
114 ).CombinedOutput()
115 if err != nil {
116 t.Fatalf("ea_restart failed: %v\n%s", err, out)
117 }
118 t.Logf("\n%s", out)
119 }
120 }
121
122 var NISTSP80090BDockerfile = `
123 FROM ubuntu:24.04
124 RUN apt-get update && apt-get install -y build-essential git \
125 libbz2-dev libdivsufsort-dev libjsoncpp-dev libgmp-dev libmpfr-dev libssl-dev \
126 && rm -rf /var/lib/apt/lists/*
127 RUN git clone --depth 1 https://github.com/usnistgov/SP800-90B_EntropyAssessment.git
128 RUN cd SP800-90B_EntropyAssessment && git checkout 8924f158c97e7b805e0f95247403ad4c44b9cd6f
129 WORKDIR ./SP800-90B_EntropyAssessment/cpp/
130 RUN make all
131 RUN cd selftest && ./selftest
132 RUN cp ea_non_iid ea_restart /usr/local/bin/
133 `
134
135 var memory entropy.ScratchBuffer
136
137
138
139
140 func samplesOrTryAgain(t *testing.T, samples []uint8) {
141 t.Helper()
142 for range 10 {
143 if err := entropy.Samples(samples, &memory); err != nil {
144 t.Logf("entropy.Samples() failed: %v", err)
145 continue
146 }
147 return
148 }
149 t.Fatal("entropy.Samples() failed 10 times in a row")
150 }
151
152 func TestEntropySHA384(t *testing.T) {
153 var input [1024]uint8
154 for i := range input {
155 input[i] = uint8(i)
156 }
157 want := sha512.Sum384(input[:])
158 got := entropy.SHA384(&input)
159 if got != want {
160 t.Errorf("SHA384() = %x, want %x", got, want)
161 }
162 }
163
164 func TestEntropyRepetitionCountTest(t *testing.T) {
165 good := bytes.Repeat(append(bytes.Repeat([]uint8{42}, 40), 1), 100)
166 if err := entropy.RepetitionCountTest(good); err != nil {
167 t.Errorf("RepetitionCountTest(good) = %v, want nil", err)
168 }
169
170 bad := bytes.Repeat([]uint8{0}, 40)
171 bad = append(bad, bytes.Repeat([]uint8{1}, 40)...)
172 bad = append(bad, bytes.Repeat([]uint8{42}, 41)...)
173 bad = append(bad, bytes.Repeat([]uint8{2}, 40)...)
174 if err := entropy.RepetitionCountTest(bad); err == nil {
175 t.Error("RepetitionCountTest(bad) = nil, want error")
176 }
177
178 bad = bytes.Repeat([]uint8{42}, 41)
179 if err := entropy.RepetitionCountTest(bad); err == nil {
180 t.Error("RepetitionCountTest(bad) = nil, want error")
181 }
182 }
183
184 func TestEntropyAdaptiveProportionTest(t *testing.T) {
185 good := bytes.Repeat([]uint8{0}, 409)
186 good = append(good, bytes.Repeat([]uint8{1}, 512-409)...)
187 good = append(good, bytes.Repeat([]uint8{0}, 409)...)
188 if err := entropy.AdaptiveProportionTest(good); err != nil {
189 t.Errorf("AdaptiveProportionTest(good) = %v, want nil", err)
190 }
191
192
193 bad := bytes.Repeat([]uint8{1}, 100)
194 bad = append(bad, bytes.Repeat([]uint8{1, 2, 3, 4, 5, 6}, 100)...)
195
196 bad = append(bad, bytes.Repeat([]uint8{42}, 410)...)
197 if err := entropy.AdaptiveProportionTest(bad[:len(bad)-1]); err != nil {
198 t.Errorf("AdaptiveProportionTest(bad[:len(bad)-1]) = %v, want nil", err)
199 }
200 if err := entropy.AdaptiveProportionTest(bad); err == nil {
201 t.Error("AdaptiveProportionTest(bad) = nil, want error")
202 }
203 }
204
205 func TestEntropyUnchanged(t *testing.T) {
206 testenv.MustHaveSource(t)
207
208 h := sha256.New()
209 root := os.DirFS("../fips140/entropy")
210 if err := fs.WalkDir(root, ".", func(path string, d fs.DirEntry, err error) error {
211 if err != nil {
212 return err
213 }
214 if d.IsDir() {
215 return nil
216 }
217 data, err := fs.ReadFile(root, path)
218 if err != nil {
219 return err
220 }
221 t.Logf("Hashing %s (%d bytes)", path, len(data))
222 fmt.Fprintf(h, "%s %d\n", path, len(data))
223 h.Write(data)
224 return nil
225 }); err != nil {
226 t.Fatalf("WalkDir: %v", err)
227 }
228
229
230
231
232
233 exp := "35976eb8a11678c79777da07aaab5511d4325701f837777df205f6e7b20c6821"
234 if got := hex.EncodeToString(h.Sum(nil)); got != exp {
235 t.Errorf("hash of crypto/internal/fips140/entropy = %s, want %s", got, exp)
236 }
237 }
238
239 func TestEntropyRace(t *testing.T) {
240
241 for range 2 {
242 go func() {
243 _, _ = entropy.Seed(&memory)
244 }()
245 }
246
247 for range 16 {
248 go func() {
249 var b [64]byte
250 drbg.Read(b[:])
251 }()
252 }
253 }
254
255 var sink byte
256
257 func BenchmarkEntropySeed(b *testing.B) {
258 for b.Loop() {
259 seed, err := entropy.Seed(&memory)
260 if err != nil {
261 b.Fatalf("entropy.Seed() failed: %v", err)
262 }
263 sink ^= seed[0]
264 }
265 }
266
View as plain text