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