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