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