1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package fips_test
19
20 import (
21 "bufio"
22 "bytes"
23 "crypto/internal/fips"
24 "crypto/internal/fips/hmac"
25 "crypto/internal/fips/sha256"
26 "crypto/internal/fips/sha3"
27 "crypto/internal/fips/sha512"
28 _ "embed"
29 "encoding/binary"
30 "encoding/json"
31 "errors"
32 "fmt"
33 "internal/testenv"
34 "io"
35 "os"
36 "os/exec"
37 "path/filepath"
38 "strings"
39 "testing"
40 )
41
42 func TestMain(m *testing.M) {
43 if os.Getenv("ACVP_WRAPPER") == "1" {
44 wrapperMain()
45 } else {
46 os.Exit(m.Run())
47 }
48 }
49
50 func wrapperMain() {
51 if err := processingLoop(bufio.NewReader(os.Stdin), os.Stdout); err != nil {
52 fmt.Fprintf(os.Stderr, "processing error: %v\n", err)
53 os.Exit(1)
54 }
55 }
56
57 type request struct {
58 name string
59 args [][]byte
60 }
61
62 type commandHandler func([][]byte) ([][]byte, error)
63
64 type command struct {
65
66 requiredArgs int
67 handler commandHandler
68 }
69
70 var (
71
72
73
74
75
76 capabilitiesJson []byte
77
78
79
80
81 commands = map[string]command{
82 "getConfig": cmdGetConfig(),
83
84 "SHA2-224": cmdHashAft(sha256.New224()),
85 "SHA2-224/MCT": cmdHashMct(sha256.New224()),
86 "SHA2-256": cmdHashAft(sha256.New()),
87 "SHA2-256/MCT": cmdHashMct(sha256.New()),
88 "SHA2-384": cmdHashAft(sha512.New384()),
89 "SHA2-384/MCT": cmdHashMct(sha512.New384()),
90 "SHA2-512": cmdHashAft(sha512.New()),
91 "SHA2-512/MCT": cmdHashMct(sha512.New()),
92 "SHA2-512/224": cmdHashAft(sha512.New512_224()),
93 "SHA2-512/224/MCT": cmdHashMct(sha512.New512_224()),
94 "SHA2-512/256": cmdHashAft(sha512.New512_256()),
95 "SHA2-512/256/MCT": cmdHashMct(sha512.New512_256()),
96
97 "SHA3-256": cmdHashAft(sha3.New256()),
98 "SHA3-256/MCT": cmdSha3Mct(sha3.New256()),
99 "SHA3-224": cmdHashAft(sha3.New224()),
100 "SHA3-224/MCT": cmdSha3Mct(sha3.New224()),
101 "SHA3-384": cmdHashAft(sha3.New384()),
102 "SHA3-384/MCT": cmdSha3Mct(sha3.New384()),
103 "SHA3-512": cmdHashAft(sha3.New512()),
104 "SHA3-512/MCT": cmdSha3Mct(sha3.New512()),
105
106 "HMAC-SHA2-224": cmdHmacAft(func() fips.Hash { return sha256.New224() }),
107 "HMAC-SHA2-256": cmdHmacAft(func() fips.Hash { return sha256.New() }),
108 "HMAC-SHA2-384": cmdHmacAft(func() fips.Hash { return sha512.New384() }),
109 "HMAC-SHA2-512": cmdHmacAft(func() fips.Hash { return sha512.New() }),
110 "HMAC-SHA2-512/224": cmdHmacAft(func() fips.Hash { return sha512.New512_224() }),
111 "HMAC-SHA2-512/256": cmdHmacAft(func() fips.Hash { return sha512.New512_256() }),
112 "HMAC-SHA3-224": cmdHmacAft(func() fips.Hash { return sha3.New224() }),
113 "HMAC-SHA3-256": cmdHmacAft(func() fips.Hash { return sha3.New256() }),
114 "HMAC-SHA3-384": cmdHmacAft(func() fips.Hash { return sha3.New384() }),
115 "HMAC-SHA3-512": cmdHmacAft(func() fips.Hash { return sha3.New512() }),
116 }
117 )
118
119 func processingLoop(reader io.Reader, writer io.Writer) error {
120
121
122
123 for {
124 req, err := readRequest(reader)
125 if errors.Is(err, io.EOF) {
126 break
127 } else if err != nil {
128 return fmt.Errorf("reading request: %w", err)
129 }
130
131 cmd, exists := commands[req.name]
132 if !exists {
133 return fmt.Errorf("unknown command: %q", req.name)
134 }
135
136 if gotArgs := len(req.args); gotArgs != cmd.requiredArgs {
137 return fmt.Errorf("command %q expected %d args, got %d", req.name, cmd.requiredArgs, gotArgs)
138 }
139
140 response, err := cmd.handler(req.args)
141 if err != nil {
142 return fmt.Errorf("command %q failed: %w", req.name, err)
143 }
144
145 if err = writeResponse(writer, response); err != nil {
146 return fmt.Errorf("command %q response failed: %w", req.name, err)
147 }
148 }
149
150 return nil
151 }
152
153 func readRequest(reader io.Reader) (*request, error) {
154
155
156
157
158
159
160 var numArgs uint32
161 if err := binary.Read(reader, binary.LittleEndian, &numArgs); err != nil {
162 return nil, err
163 }
164 if numArgs == 0 {
165 return nil, errors.New("invalid request: zero args")
166 }
167
168 args, err := readArgs(reader, numArgs)
169 if err != nil {
170 return nil, err
171 }
172
173 return &request{
174 name: string(args[0]),
175 args: args[1:],
176 }, nil
177 }
178
179 func readArgs(reader io.Reader, requiredArgs uint32) ([][]byte, error) {
180 argLengths := make([]uint32, requiredArgs)
181 args := make([][]byte, requiredArgs)
182
183 for i := range argLengths {
184 if err := binary.Read(reader, binary.LittleEndian, &argLengths[i]); err != nil {
185 return nil, fmt.Errorf("invalid request: failed to read %d-th arg len: %w", i, err)
186 }
187 }
188
189 for i, length := range argLengths {
190 buf := make([]byte, length)
191 if _, err := io.ReadFull(reader, buf); err != nil {
192 return nil, fmt.Errorf("invalid request: failed to read %d-th arg data: %w", i, err)
193 }
194 args[i] = buf
195 }
196
197 return args, nil
198 }
199
200 func writeResponse(writer io.Writer, args [][]byte) error {
201
202
203
204 numArgs := uint32(len(args))
205 if err := binary.Write(writer, binary.LittleEndian, numArgs); err != nil {
206 return fmt.Errorf("writing arg count: %w", err)
207 }
208
209 for i, arg := range args {
210 if err := binary.Write(writer, binary.LittleEndian, uint32(len(arg))); err != nil {
211 return fmt.Errorf("writing %d-th arg length: %w", i, err)
212 }
213 }
214
215 for i, b := range args {
216 if _, err := writer.Write(b); err != nil {
217 return fmt.Errorf("writing %d-th arg data: %w", i, err)
218 }
219 }
220
221 return nil
222 }
223
224
225
226
227 func cmdGetConfig() command {
228 return command{
229 handler: func(args [][]byte) ([][]byte, error) {
230 return [][]byte{capabilitiesJson}, nil
231 },
232 }
233 }
234
235
236
237
238
239
240
241
242 func cmdHashAft(h fips.Hash) command {
243 return command{
244 requiredArgs: 1,
245 handler: func(args [][]byte) ([][]byte, error) {
246 h.Reset()
247 h.Write(args[0])
248 digest := make([]byte, 0, h.Size())
249 digest = h.Sum(digest)
250
251 return [][]byte{digest}, nil
252 },
253 }
254 }
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270 func cmdHashMct(h fips.Hash) command {
271 return command{
272 requiredArgs: 1,
273 handler: func(args [][]byte) ([][]byte, error) {
274 hSize := h.Size()
275 seed := args[0]
276
277 if seedLen := len(seed); seedLen != hSize {
278 return nil, fmt.Errorf("invalid seed size: expected %d got %d", hSize, seedLen)
279 }
280
281 digest := make([]byte, 0, hSize)
282 buf := make([]byte, 0, 3*hSize)
283 buf = append(buf, seed...)
284 buf = append(buf, seed...)
285 buf = append(buf, seed...)
286
287 for i := 0; i < 1000; i++ {
288 h.Reset()
289 h.Write(buf)
290 digest = h.Sum(digest[:0])
291
292 copy(buf, buf[hSize:])
293 copy(buf[2*hSize:], digest)
294 }
295
296 return [][]byte{buf[hSize*2:]}, nil
297 },
298 }
299 }
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314 func cmdSha3Mct(h fips.Hash) command {
315 return command{
316 requiredArgs: 1,
317 handler: func(args [][]byte) ([][]byte, error) {
318 seed := args[0]
319 md := make([][]byte, 1001)
320 md[0] = seed
321
322 for i := 1; i <= 1000; i++ {
323 h.Reset()
324 h.Write(md[i-1])
325 md[i] = h.Sum(nil)
326 }
327
328 return [][]byte{md[1000]}, nil
329 },
330 }
331 }
332
333 func cmdHmacAft(h func() fips.Hash) command {
334 return command{
335 requiredArgs: 2,
336 handler: func(args [][]byte) ([][]byte, error) {
337 msg := args[0]
338 key := args[1]
339 mac := hmac.New(h, key)
340 mac.Write(msg)
341 return [][]byte{mac.Sum(nil)}, nil
342 },
343 }
344 }
345
346 func TestACVP(t *testing.T) {
347 testenv.SkipIfShortAndSlow(t)
348 testenv.MustHaveExternalNetwork(t)
349 testenv.MustHaveGoRun(t)
350 testenv.MustHaveExec(t)
351
352 const (
353 bsslModule = "boringssl.googlesource.com/boringssl.git"
354 bsslVersion = "v0.0.0-20241009223352-905c3903fd42"
355 goAcvpModule = "github.com/cpu/go-acvp"
356 goAcvpVersion = "v0.0.0-20241009200939-159f4c69a90d"
357 )
358
359
360
361
362
363
364 if _, err := os.Stat("acvp_test.config.json"); err != nil {
365 t.Fatalf("failed to stat config file: %s", err)
366 }
367
368
369 d := t.TempDir()
370 modcache := filepath.Join(d, "modcache")
371 if err := os.Mkdir(modcache, 0777); err != nil {
372 t.Fatal(err)
373 }
374 fmt.Printf("caching dependent modules in %q\n", modcache)
375 t.Setenv("GOMODCACHE", modcache)
376
377
378 bsslDir := fetchModule(t, bsslModule, bsslVersion)
379
380 fmt.Println("building acvptool")
381
382
383 goTool := testenv.GoToolPath(t)
384 cmd := exec.Command(goTool,
385 "build",
386 "./util/fipstools/acvp/acvptool")
387 cmd.Dir = bsslDir
388 out := &strings.Builder{}
389 cmd.Stderr = out
390 if err := cmd.Run(); err != nil {
391 t.Fatalf("failed to build acvptool: %s\n%s", err, out.String())
392 }
393
394
395 dataDir := fetchModule(t, goAcvpModule, goAcvpVersion)
396
397 cwd, err := os.Getwd()
398 if err != nil {
399 t.Fatalf("failed to fetch cwd: %s", err)
400 }
401 configPath := filepath.Join(cwd, "acvp_test.config.json")
402 toolPath := filepath.Join(bsslDir, "acvptool")
403 fmt.Printf("running check_expected.go\ncwd: %q\ndata_dir: %q\nconfig: %q\ntool: %q\nmodule-wrapper: %q\n",
404 cwd, dataDir, configPath, toolPath, os.Args[0])
405
406
407
408
409 args := []string{
410 "run",
411 filepath.Join(bsslDir, "util/fipstools/acvp/acvptool/test/check_expected.go"),
412 "-tool",
413 toolPath,
414
415 "-module-wrappers", "go:" + os.Args[0],
416 "-tests", configPath,
417 }
418 cmd = exec.Command(goTool, args...)
419 cmd.Dir = dataDir
420 cmd.Env = []string{"ACVP_WRAPPER=1", "GOCACHE=" + modcache}
421 output, err := cmd.CombinedOutput()
422 if err != nil {
423 t.Fatalf("failed to run acvp tests: %s\n%s", err, string(output))
424 }
425 fmt.Println(string(output))
426 }
427
428 func fetchModule(t *testing.T, module, version string) string {
429 goTool := testenv.GoToolPath(t)
430 fmt.Printf("fetching %s@%s\n", module, version)
431
432 output, err := exec.Command(goTool, "mod", "download", "-json", "-modcacherw", module+"@"+version).CombinedOutput()
433 if err != nil {
434 t.Fatalf("failed to download %s@%s: %s\n%s\n", module, version, err, output)
435 }
436 var j struct {
437 Dir string
438 }
439 if err := json.Unmarshal(output, &j); err != nil {
440 t.Fatalf("failed to parse 'go mod download': %s\n%s\n", err, output)
441 }
442
443 return j.Dir
444 }
445
446 func TestTooFewArgs(t *testing.T) {
447 commands["test"] = command{
448 requiredArgs: 1,
449 handler: func(args [][]byte) ([][]byte, error) {
450 if gotArgs := len(args); gotArgs != 1 {
451 return nil, fmt.Errorf("expected 1 args, got %d", gotArgs)
452 }
453 return nil, nil
454 },
455 }
456
457 var output bytes.Buffer
458 err := processingLoop(mockRequest(t, "test", nil), &output)
459 if err == nil {
460 t.Fatalf("expected error, got nil")
461 }
462 expectedErr := "expected 1 args, got 0"
463 if !strings.Contains(err.Error(), expectedErr) {
464 t.Errorf("expected error to contain %q, got %v", expectedErr, err)
465 }
466 }
467
468 func TestTooManyArgs(t *testing.T) {
469 commands["test"] = command{
470 requiredArgs: 1,
471 handler: func(args [][]byte) ([][]byte, error) {
472 if gotArgs := len(args); gotArgs != 1 {
473 return nil, fmt.Errorf("expected 1 args, got %d", gotArgs)
474 }
475 return nil, nil
476 },
477 }
478
479 var output bytes.Buffer
480 err := processingLoop(mockRequest(
481 t, "test", [][]byte{[]byte("one"), []byte("two")}), &output)
482 if err == nil {
483 t.Fatalf("expected error, got nil")
484 }
485 expectedErr := "expected 1 args, got 2"
486 if !strings.Contains(err.Error(), expectedErr) {
487 t.Errorf("expected error to contain %q, got %v", expectedErr, err)
488 }
489 }
490
491 func TestGetConfig(t *testing.T) {
492 var output bytes.Buffer
493 err := processingLoop(mockRequest(t, "getConfig", nil), &output)
494 if err != nil {
495 t.Errorf("unexpected error: %v", err)
496 }
497
498 respArgs := readResponse(t, &output)
499 if len(respArgs) != 1 {
500 t.Fatalf("expected 1 response arg, got %d", len(respArgs))
501 }
502
503 if !bytes.Equal(respArgs[0], capabilitiesJson) {
504 t.Errorf("expected config %q, got %q", string(capabilitiesJson), string(respArgs[0]))
505 }
506 }
507
508 func TestSha2256(t *testing.T) {
509 testMessage := []byte("gophers eat grass")
510 expectedDigest := []byte{
511 188, 142, 10, 214, 48, 236, 72, 143, 70, 216, 223, 205, 219, 69, 53, 29,
512 205, 207, 162, 6, 14, 70, 113, 60, 251, 170, 201, 236, 119, 39, 141, 172,
513 }
514
515 var output bytes.Buffer
516 err := processingLoop(mockRequest(t, "SHA2-256", [][]byte{testMessage}), &output)
517 if err != nil {
518 t.Errorf("unexpected error: %v", err)
519 }
520
521 respArgs := readResponse(t, &output)
522 if len(respArgs) != 1 {
523 t.Fatalf("expected 1 response arg, got %d", len(respArgs))
524 }
525
526 if !bytes.Equal(respArgs[0], expectedDigest) {
527 t.Errorf("expected digest %v, got %v", expectedDigest, respArgs[0])
528 }
529 }
530
531 func mockRequest(t *testing.T, cmd string, args [][]byte) io.Reader {
532 t.Helper()
533
534 msgData := append([][]byte{[]byte(cmd)}, args...)
535
536 var buf bytes.Buffer
537 if err := writeResponse(&buf, msgData); err != nil {
538 t.Fatalf("writeResponse error: %v", err)
539 }
540
541 return &buf
542 }
543
544 func readResponse(t *testing.T, reader io.Reader) [][]byte {
545 var numArgs uint32
546 if err := binary.Read(reader, binary.LittleEndian, &numArgs); err != nil {
547 t.Fatalf("failed to read response args count: %v", err)
548 }
549
550 args, err := readArgs(reader, numArgs)
551 if err != nil {
552 t.Fatalf("failed to read %d response args: %v", numArgs, err)
553 }
554
555 return args
556 }
557
View as plain text