• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// run_cavp.go processes CAVP input files and generates suitable response
2// files, optionally comparing the results against the provided FAX files.
3package main
4
5import (
6	"bufio"
7	"errors"
8	"flag"
9	"fmt"
10	"os"
11	"os/exec"
12	"path"
13	"path/filepath"
14	"runtime"
15	"strings"
16	"sync"
17	"time"
18)
19
20var (
21	oraclePath = flag.String("oracle-bin", "", "Path to the oracle binary")
22	suiteDir   = flag.String("suite-dir", "", "Base directory containing the CAVP test suite")
23	noFAX      = flag.Bool("no-fax", false, "Skip comparing against FAX files")
24	niap       = flag.Bool("niap", false, "Perform NIAP tests rather than FIPS tests")
25	android    = flag.Bool("android", false, "Run tests via ADB")
26)
27
28const (
29	androidTmpPath       = "/data/local/tmp/"
30	androidCAVPPath      = androidTmpPath + "cavp"
31	androidLibCryptoPath = androidTmpPath + "libcrypto.so"
32)
33
34// test describes a single request file.
35type test struct {
36	// inFile is the base of the filename without an extension, i.e.
37	// “ECBMCT128”.
38	inFile string
39	// args are the arguments (not including the input filename) to the
40	// oracle binary.
41	args []string
42	// noFAX, if true, indicates that the output cannot be compared against
43	// the FAX file. (E.g. because the primitive is non-deterministic.)
44	noFAX bool
45}
46
47// nextLineState can be used by FAX next-line function to store state.
48type nextLineState struct {
49	// State used by the KAS test.
50	nextIsIUTHash bool
51}
52
53// testSuite describes a series of tests that are handled by a single oracle
54// binary.
55type testSuite struct {
56	// directory is the name of the directory in the CAVP input, i.e. “AES”.
57	directory string
58	// suite names the test suite to pass as the first command-line argument.
59	suite string
60	// nextLineFunc, if not nil, is the function used to read the next line
61	// from the FAX file. This can be used to skip lines and/or mutate them
62	// as needed. The second argument can be used by the scanner to store
63	// state, if needed. If isWildcard is true on return then line is not
64	// meaningful and any line from the response file should be accepted.
65	nextLineFunc func(*bufio.Scanner, *nextLineState) (line string, isWildcard, ok bool)
66	tests        []test
67}
68
69func (t *testSuite) getDirectory() string {
70	return filepath.Join(*suiteDir, t.directory)
71}
72
73var aesGCMTests = testSuite{
74	"AES_GCM",
75	"aes_gcm",
76	nil,
77	[]test{
78		{"gcmDecrypt128", []string{"dec", "aes-128-gcm"}, false},
79		{"gcmDecrypt256", []string{"dec", "aes-256-gcm"}, false},
80		{"gcmEncryptExtIV128", []string{"enc", "aes-128-gcm"}, false},
81		{"gcmEncryptExtIV256", []string{"enc", "aes-256-gcm"}, false},
82	},
83}
84
85var aesTests = testSuite{
86	"AES",
87	"aes",
88	nil,
89	[]test{
90		{"CBCGFSbox128", []string{"kat", "aes-128-cbc"}, false},
91		{"CBCGFSbox192", []string{"kat", "aes-192-cbc"}, false},
92		{"CBCGFSbox256", []string{"kat", "aes-256-cbc"}, false},
93		{"CBCKeySbox128", []string{"kat", "aes-128-cbc"}, false},
94		{"CBCKeySbox192", []string{"kat", "aes-192-cbc"}, false},
95		{"CBCKeySbox256", []string{"kat", "aes-256-cbc"}, false},
96		{"CBCMMT128", []string{"kat", "aes-128-cbc"}, false},
97		{"CBCMMT192", []string{"kat", "aes-192-cbc"}, false},
98		{"CBCMMT256", []string{"kat", "aes-256-cbc"}, false},
99		{"CBCVarKey128", []string{"kat", "aes-128-cbc"}, false},
100		{"CBCVarKey192", []string{"kat", "aes-192-cbc"}, false},
101		{"CBCVarKey256", []string{"kat", "aes-256-cbc"}, false},
102		{"CBCVarTxt128", []string{"kat", "aes-128-cbc"}, false},
103		{"CBCVarTxt192", []string{"kat", "aes-192-cbc"}, false},
104		{"CBCVarTxt256", []string{"kat", "aes-256-cbc"}, false},
105		{"ECBGFSbox128", []string{"kat", "aes-128-ecb"}, false},
106		{"ECBGFSbox192", []string{"kat", "aes-192-ecb"}, false},
107		{"ECBGFSbox256", []string{"kat", "aes-256-ecb"}, false},
108		{"ECBKeySbox128", []string{"kat", "aes-128-ecb"}, false},
109		{"ECBKeySbox192", []string{"kat", "aes-192-ecb"}, false},
110		{"ECBKeySbox256", []string{"kat", "aes-256-ecb"}, false},
111		{"ECBMMT128", []string{"kat", "aes-128-ecb"}, false},
112		{"ECBMMT192", []string{"kat", "aes-192-ecb"}, false},
113		{"ECBMMT256", []string{"kat", "aes-256-ecb"}, false},
114		{"ECBVarKey128", []string{"kat", "aes-128-ecb"}, false},
115		{"ECBVarKey192", []string{"kat", "aes-192-ecb"}, false},
116		{"ECBVarKey256", []string{"kat", "aes-256-ecb"}, false},
117		{"ECBVarTxt128", []string{"kat", "aes-128-ecb"}, false},
118		{"ECBVarTxt192", []string{"kat", "aes-192-ecb"}, false},
119		{"ECBVarTxt256", []string{"kat", "aes-256-ecb"}, false},
120		// AES Monte-Carlo tests
121		{"ECBMCT128", []string{"mct", "aes-128-ecb"}, false},
122		{"ECBMCT192", []string{"mct", "aes-192-ecb"}, false},
123		{"ECBMCT256", []string{"mct", "aes-256-ecb"}, false},
124		{"CBCMCT128", []string{"mct", "aes-128-cbc"}, false},
125		{"CBCMCT192", []string{"mct", "aes-192-cbc"}, false},
126		{"CBCMCT256", []string{"mct", "aes-256-cbc"}, false},
127	},
128}
129
130var ecdsa2KeyPairTests = testSuite{
131	"ECDSA2",
132	"ecdsa2_keypair",
133	nil,
134	[]test{{"KeyPair", nil, true}},
135}
136
137var ecdsa2PKVTests = testSuite{
138	"ECDSA2",
139	"ecdsa2_pkv",
140	nil,
141	[]test{{"PKV", nil, false}},
142}
143
144var ecdsa2SigGenTests = testSuite{
145	"ECDSA2",
146	"ecdsa2_siggen",
147	nil,
148	[]test{
149		{"SigGen", []string{"SigGen"}, true},
150		{"SigGenComponent", []string{"SigGenComponent"}, true},
151	},
152}
153
154var ecdsa2SigVerTests = testSuite{
155	"ECDSA2",
156	"ecdsa2_sigver",
157	nil,
158	[]test{{"SigVer", nil, false}},
159}
160
161var rsa2KeyGenTests = testSuite{
162	"RSA2",
163	"rsa2_keygen",
164	nil,
165	[]test{
166		{"KeyGen_RandomProbablyPrime3_3", nil, true},
167	},
168}
169
170var rsa2SigGenTests = testSuite{
171	"RSA2",
172	"rsa2_siggen",
173	nil,
174	[]test{
175		{"SigGen15_186-3", []string{"pkcs15"}, true},
176		{"SigGenPSS_186-3", []string{"pss"}, true},
177	},
178}
179
180var rsa2SigVerTests = testSuite{
181	"RSA2",
182	"rsa2_sigver",
183	func(s *bufio.Scanner, state *nextLineState) (string, bool, bool) {
184		for {
185			if !s.Scan() {
186				return "", false, false
187			}
188
189			line := s.Text()
190			if strings.HasPrefix(line, "p = ") || strings.HasPrefix(line, "d = ") || strings.HasPrefix(line, "SaltVal = ") || strings.HasPrefix(line, "EM with ") {
191				continue
192			}
193			if strings.HasPrefix(line, "q = ") {
194				// Skip the "q = " line and an additional blank line.
195				if !s.Scan() ||
196					len(strings.TrimSpace(s.Text())) > 0 {
197					return "", false, false
198				}
199				continue
200			}
201			return line, false, true
202		}
203	},
204	[]test{
205		{"SigVer15_186-3", []string{"pkcs15"}, false},
206		{"SigVerPSS_186-3", []string{"pss"}, false},
207	},
208}
209
210var hmacTests = testSuite{
211	"HMAC",
212	"hmac",
213	nil,
214	[]test{{"HMAC", nil, false}},
215}
216
217var shaTests = testSuite{
218	"SHA",
219	"sha",
220	nil,
221	[]test{
222		{"SHA1LongMsg", []string{"SHA1"}, false},
223		{"SHA1ShortMsg", []string{"SHA1"}, false},
224		{"SHA224LongMsg", []string{"SHA224"}, false},
225		{"SHA224ShortMsg", []string{"SHA224"}, false},
226		{"SHA256LongMsg", []string{"SHA256"}, false},
227		{"SHA256ShortMsg", []string{"SHA256"}, false},
228		{"SHA384LongMsg", []string{"SHA384"}, false},
229		{"SHA384ShortMsg", []string{"SHA384"}, false},
230		{"SHA512LongMsg", []string{"SHA512"}, false},
231		{"SHA512ShortMsg", []string{"SHA512"}, false},
232	},
233}
234
235var shaMonteTests = testSuite{
236	"SHA",
237	"sha_monte",
238	nil,
239	[]test{
240		{"SHA1Monte", []string{"SHA1"}, false},
241		{"SHA224Monte", []string{"SHA224"}, false},
242		{"SHA256Monte", []string{"SHA256"}, false},
243		{"SHA384Monte", []string{"SHA384"}, false},
244		{"SHA512Monte", []string{"SHA512"}, false},
245	},
246}
247
248var ctrDRBGTests = testSuite{
249	"DRBG800-90A",
250	"ctr_drbg",
251	nil,
252	[]test{{"CTR_DRBG", nil, false}},
253}
254
255var tdesTests = testSuite{
256	"TDES",
257	"tdes",
258	nil,
259	[]test{
260		{"TCBCMMT2", []string{"kat", "des-ede-cbc"}, false},
261		{"TCBCMMT3", []string{"kat", "des-ede3-cbc"}, false},
262		{"TCBCMonte2", []string{"mct", "des-ede3-cbc"}, false},
263		{"TCBCMonte3", []string{"mct", "des-ede3-cbc"}, false},
264		{"TCBCinvperm", []string{"kat", "des-ede3-cbc"}, false},
265		{"TCBCpermop", []string{"kat", "des-ede3-cbc"}, false},
266		{"TCBCsubtab", []string{"kat", "des-ede3-cbc"}, false},
267		{"TCBCvarkey", []string{"kat", "des-ede3-cbc"}, false},
268		{"TCBCvartext", []string{"kat", "des-ede3-cbc"}, false},
269		{"TECBMMT2", []string{"kat", "des-ede"}, false},
270		{"TECBMMT3", []string{"kat", "des-ede3"}, false},
271		{"TECBMonte2", []string{"mct", "des-ede3"}, false},
272		{"TECBMonte3", []string{"mct", "des-ede3"}, false},
273		{"TECBinvperm", []string{"kat", "des-ede3"}, false},
274		{"TECBpermop", []string{"kat", "des-ede3"}, false},
275		{"TECBsubtab", []string{"kat", "des-ede3"}, false},
276		{"TECBvarkey", []string{"kat", "des-ede3"}, false},
277		{"TECBvartext", []string{"kat", "des-ede3"}, false},
278	},
279}
280
281var keyWrapTests = testSuite{
282	"KeyWrap38F",
283	"keywrap",
284	nil,
285	[]test{
286		{"KW_AD_128", []string{"dec", "128"}, false},
287		{"KW_AD_256", []string{"dec", "256"}, false},
288		{"KW_AE_128", []string{"enc", "128"}, false},
289		{"KW_AE_256", []string{"enc", "256"}, false},
290	},
291}
292
293var kasTests = testSuite{
294	"KAS",
295	"kas",
296	func(s *bufio.Scanner, state *nextLineState) (line string, isWildcard, ok bool) {
297		for {
298			// If the response file will include the IUT hash next,
299			// return a wildcard signal because this cannot be
300			// matched against the FAX file.
301			if state.nextIsIUTHash {
302				state.nextIsIUTHash = false
303				return "", true, true
304			}
305
306			if !s.Scan() {
307				return "", false, false
308			}
309
310			line := s.Text()
311			if strings.HasPrefix(line, "deCAVS = ") || strings.HasPrefix(line, "Z = ") {
312				continue
313			}
314			if strings.HasPrefix(line, "CAVSHashZZ = ") {
315				state.nextIsIUTHash = true
316			}
317			return line, false, true
318		}
319	},
320	[]test{
321		{"KASFunctionTest_ECCEphemeralUnified_NOKC_ZZOnly_init", []string{"function"}, true},
322		{"KASFunctionTest_ECCEphemeralUnified_NOKC_ZZOnly_resp", []string{"function"}, true},
323		{"KASValidityTest_ECCEphemeralUnified_NOKC_ZZOnly_init", []string{"validity"}, false},
324		{"KASValidityTest_ECCEphemeralUnified_NOKC_ZZOnly_resp", []string{"validity"}, false},
325	},
326}
327
328var tlsKDFTests = testSuite{
329	"KDF135",
330	"tlskdf",
331	nil,
332	[]test{
333		{"tls", nil, false},
334	},
335}
336
337var fipsTestSuites = []*testSuite{
338	&aesGCMTests,
339	&aesTests,
340	&ctrDRBGTests,
341	&ecdsa2KeyPairTests,
342	&ecdsa2PKVTests,
343	&ecdsa2SigGenTests,
344	&ecdsa2SigVerTests,
345	&hmacTests,
346	&keyWrapTests,
347	&rsa2KeyGenTests,
348	&rsa2SigGenTests,
349	&rsa2SigVerTests,
350	&shaTests,
351	&shaMonteTests,
352	&tdesTests,
353}
354
355var niapTestSuites = []*testSuite{
356	&kasTests,
357	&tlsKDFTests,
358}
359
360// testInstance represents a specific test in a testSuite.
361type testInstance struct {
362	suite     *testSuite
363	testIndex int
364}
365
366func worker(wg *sync.WaitGroup, work <-chan testInstance) {
367	defer wg.Done()
368
369	for ti := range work {
370		test := ti.suite.tests[ti.testIndex]
371
372		if err := doTest(ti.suite, test); err != nil {
373			fmt.Fprintf(os.Stderr, "%s\n", err)
374			os.Exit(2)
375		}
376
377		if !*noFAX && !test.noFAX {
378			if err := compareFAX(ti.suite, test); err != nil {
379				fmt.Fprintf(os.Stderr, "%s\n", err)
380				os.Exit(3)
381			}
382		}
383	}
384}
385
386func checkAndroidPrereqs() error {
387	// The cavp binary, and a matching libcrypto.so, are required to be placed
388	// in /data/local/tmp before running this script.
389	if err := exec.Command("adb", "shell", "ls", androidCAVPPath).Run(); err != nil {
390		return errors.New("failed to list cavp binary; ensure that adb works and cavp binary is in place: " + err.Error())
391	}
392	if err := exec.Command("adb", "shell", "ls", androidLibCryptoPath).Run(); err != nil {
393		return errors.New("failed to list libcrypto.so; ensure that library is in place: " + err.Error())
394	}
395	return nil
396}
397
398func main() {
399	flag.Parse()
400
401	if *android {
402		if err := checkAndroidPrereqs(); err != nil {
403			fmt.Fprintf(os.Stderr, "%s\n", err)
404			os.Exit(1)
405		}
406	} else if len(*oraclePath) == 0 {
407		fmt.Fprintf(os.Stderr, "Must give -oracle-bin\n")
408		os.Exit(1)
409	}
410
411	work := make(chan testInstance)
412	var wg sync.WaitGroup
413
414	numWorkers := runtime.NumCPU()
415	if *android {
416		numWorkers = 1
417	}
418
419	for i := 0; i < numWorkers; i++ {
420		wg.Add(1)
421		go worker(&wg, work)
422	}
423
424	testSuites := fipsTestSuites
425	if *niap {
426		testSuites = niapTestSuites
427	}
428
429	for _, suite := range testSuites {
430		for i := range suite.tests {
431			work <- testInstance{suite, i}
432		}
433	}
434
435	close(work)
436	wg.Wait()
437}
438
439func doTest(suite *testSuite, test test) error {
440	bin := *oraclePath
441	var args []string
442
443	if *android {
444		bin = "adb"
445		args = []string{"shell", "LD_LIBRARY_PATH=" + androidTmpPath, androidCAVPPath}
446	}
447
448	args = append(args, suite.suite)
449	args = append(args, test.args...)
450	reqPath := filepath.Join(suite.getDirectory(), "req", test.inFile+".req")
451	var reqPathOnDevice string
452
453	if *android {
454		reqPathOnDevice = path.Join(androidTmpPath, test.inFile+".req")
455		if err := exec.Command("adb", "push", reqPath, reqPathOnDevice).Run(); err != nil {
456			return errors.New("failed to push request file: " + err.Error())
457		}
458		args = append(args, reqPathOnDevice)
459	} else {
460		args = append(args, reqPath)
461	}
462
463	respDir := filepath.Join(suite.getDirectory(), "resp")
464	if err := os.Mkdir(respDir, 0755); err != nil && !os.IsExist(err) {
465		return fmt.Errorf("cannot create resp directory: %s", err)
466	}
467	outPath := filepath.Join(respDir, test.inFile+".rsp")
468	outFile, err := os.OpenFile(outPath, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644)
469	if err != nil {
470		return fmt.Errorf("cannot open output file for %q %q: %s", suite.getDirectory(), test.inFile, err)
471	}
472	defer outFile.Close()
473
474	cmd := exec.Command(bin, args...)
475	cmd.Stdout = outFile
476	cmd.Stderr = os.Stderr
477
478	cmdLine := strings.Join(append([]string{bin}, args...), " ")
479	startTime := time.Now()
480	if err := cmd.Run(); err != nil {
481		return fmt.Errorf("cannot run command for %q %q (%s): %s", suite.getDirectory(), test.inFile, cmdLine, err)
482	}
483
484	fmt.Printf("%s (%ds)\n", cmdLine, int(time.Since(startTime).Seconds()))
485
486	if *android {
487		exec.Command("adb", "shell", "rm", reqPathOnDevice).Run()
488	}
489
490	return nil
491}
492
493func canonicalizeLine(in string) string {
494	if strings.HasPrefix(in, "Result = P (") {
495		return "Result = P"
496	}
497	if strings.HasPrefix(in, "Result = F (") {
498		return "Result = F"
499	}
500	return in
501}
502
503func compareFAX(suite *testSuite, test test) error {
504	nextLineFunc := suite.nextLineFunc
505	if nextLineFunc == nil {
506		nextLineFunc = func(s *bufio.Scanner, state *nextLineState) (string, bool, bool) {
507			if !s.Scan() {
508				return "", false, false
509			}
510			return s.Text(), false, true
511		}
512	}
513
514	respPath := filepath.Join(suite.getDirectory(), "resp", test.inFile+".rsp")
515	respFile, err := os.Open(respPath)
516	if err != nil {
517		return fmt.Errorf("cannot read output of %q %q: %s", suite.getDirectory(), test.inFile, err)
518	}
519	defer respFile.Close()
520
521	faxPath := filepath.Join(suite.getDirectory(), "fax", test.inFile+".fax")
522	faxFile, err := os.Open(faxPath)
523	if err != nil {
524		return fmt.Errorf("cannot open fax file for %q %q: %s", suite.getDirectory(), test.inFile, err)
525	}
526	defer faxFile.Close()
527
528	respScanner := bufio.NewScanner(respFile)
529	faxScanner := bufio.NewScanner(faxFile)
530	var nextLineState nextLineState
531
532	lineNo := 0
533	inHeader := true
534
535	for respScanner.Scan() {
536		lineNo++
537		respLine := respScanner.Text()
538		var faxLine string
539		var isWildcard, ok bool
540
541		if inHeader && (len(respLine) == 0 || respLine[0] == '#') {
542			continue
543		}
544
545		for {
546			haveFaxLine := false
547
548			if inHeader {
549				for {
550					if faxLine, isWildcard, ok = nextLineFunc(faxScanner, &nextLineState); !ok {
551						break
552					}
553					if len(faxLine) != 0 && faxLine[0] != '#' {
554						haveFaxLine = true
555						break
556					}
557				}
558
559				inHeader = false
560			} else {
561				faxLine, isWildcard, haveFaxLine = nextLineFunc(faxScanner, &nextLineState)
562			}
563
564			if !haveFaxLine {
565				// Ignore blank lines at the end of the generated file.
566				if len(respLine) == 0 {
567					break
568				}
569				return fmt.Errorf("resp file is longer than fax for %q %q", suite.getDirectory(), test.inFile)
570			}
571
572			if strings.HasPrefix(faxLine, " (Reason: ") {
573				continue
574			}
575
576			break
577		}
578
579		if isWildcard || canonicalizeLine(faxLine) == canonicalizeLine(respLine) {
580			continue
581		}
582
583		return fmt.Errorf("resp and fax differ at line %d for %q %q: %q vs %q", lineNo, suite.getDirectory(), test.inFile, respLine, faxLine)
584	}
585
586	if _, _, ok := nextLineFunc(faxScanner, &nextLineState); ok {
587		return fmt.Errorf("fax file is longer than resp for %q %q", suite.getDirectory(), test.inFile)
588	}
589
590	return nil
591}
592