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