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