diff options
Diffstat (limited to 'src/util/fipstools/cavp/run_cavp.go')
-rw-r--r-- | src/util/fipstools/cavp/run_cavp.go | 590 |
1 files changed, 590 insertions, 0 deletions
diff --git a/src/util/fipstools/cavp/run_cavp.go b/src/util/fipstools/cavp/run_cavp.go new file mode 100644 index 00000000..b37f4532 --- /dev/null +++ b/src/util/fipstools/cavp/run_cavp.go @@ -0,0 +1,590 @@ +// run_cavp.go processes CAVP input files and generates suitable response +// files, optionally comparing the results against the provided FAX files. +package main + +import ( + "bufio" + "errors" + "flag" + "fmt" + "os" + "os/exec" + "path" + "path/filepath" + "runtime" + "strings" + "sync" + "time" +) + +var ( + oraclePath = flag.String("oracle-bin", "", "Path to the oracle binary") + suiteDir = flag.String("suite-dir", "", "Base directory containing the CAVP test suite") + noFAX = flag.Bool("no-fax", false, "Skip comparing against FAX files") + android = flag.Bool("android", false, "Run tests via ADB") +) + +const ( + androidTmpPath = "/data/local/tmp/" + androidCAVPPath = androidTmpPath + "cavp" + androidLibCryptoPath = androidTmpPath + "libcrypto.so" +) + +// test describes a single request file. +type test struct { + // inFile is the base of the filename without an extension, i.e. + // “ECBMCT128”. + inFile string + // args are the arguments (not including the input filename) to the + // oracle binary. + args []string + // noFAX, if true, indicates that the output cannot be compared against + // the FAX file. (E.g. because the primitive is non-deterministic.) + noFAX bool +} + +// nextLineState can be used by FAX next-line function to store state. +type nextLineState struct { + // State used by the KAS test. + nextIsIUTHash bool +} + +// testSuite describes a series of tests that are handled by a single oracle +// binary. +type testSuite struct { + // directory is the name of the directory in the CAVP input, i.e. “AES”. + directory string + // suite names the test suite to pass as the first command-line argument. + suite string + // nextLineFunc, if not nil, is the function used to read the next line + // from the FAX file. This can be used to skip lines and/or mutate them + // as needed. The second argument can be used by the scanner to store + // state, if needed. If isWildcard is true on return then line is not + // meaningful and any line from the response file should be accepted. + nextLineFunc func(*bufio.Scanner, *nextLineState) (line string, isWildcard, ok bool) + tests []test +} + +func (t *testSuite) getDirectory() string { + return filepath.Join(*suiteDir, t.directory) +} + +var aesGCMTests = testSuite{ + "AES_GCM", + "aes_gcm", + nil, + []test{ + {"gcmDecrypt128", []string{"dec", "aes-128-gcm"}, false}, + {"gcmDecrypt256", []string{"dec", "aes-256-gcm"}, false}, + {"gcmEncryptExtIV128", []string{"enc", "aes-128-gcm"}, false}, + {"gcmEncryptExtIV256", []string{"enc", "aes-256-gcm"}, false}, + }, +} + +var aesTests = testSuite{ + "AES", + "aes", + nil, + []test{ + {"CBCGFSbox128", []string{"kat", "aes-128-cbc"}, false}, + {"CBCGFSbox192", []string{"kat", "aes-192-cbc"}, false}, + {"CBCGFSbox256", []string{"kat", "aes-256-cbc"}, false}, + {"CBCKeySbox128", []string{"kat", "aes-128-cbc"}, false}, + {"CBCKeySbox192", []string{"kat", "aes-192-cbc"}, false}, + {"CBCKeySbox256", []string{"kat", "aes-256-cbc"}, false}, + {"CBCMMT128", []string{"kat", "aes-128-cbc"}, false}, + {"CBCMMT192", []string{"kat", "aes-192-cbc"}, false}, + {"CBCMMT256", []string{"kat", "aes-256-cbc"}, false}, + {"CBCVarKey128", []string{"kat", "aes-128-cbc"}, false}, + {"CBCVarKey192", []string{"kat", "aes-192-cbc"}, false}, + {"CBCVarKey256", []string{"kat", "aes-256-cbc"}, false}, + {"CBCVarTxt128", []string{"kat", "aes-128-cbc"}, false}, + {"CBCVarTxt192", []string{"kat", "aes-192-cbc"}, false}, + {"CBCVarTxt256", []string{"kat", "aes-256-cbc"}, false}, + {"ECBGFSbox128", []string{"kat", "aes-128-ecb"}, false}, + {"ECBGFSbox192", []string{"kat", "aes-192-ecb"}, false}, + {"ECBGFSbox256", []string{"kat", "aes-256-ecb"}, false}, + {"ECBKeySbox128", []string{"kat", "aes-128-ecb"}, false}, + {"ECBKeySbox192", []string{"kat", "aes-192-ecb"}, false}, + {"ECBKeySbox256", []string{"kat", "aes-256-ecb"}, false}, + {"ECBMMT128", []string{"kat", "aes-128-ecb"}, false}, + {"ECBMMT192", []string{"kat", "aes-192-ecb"}, false}, + {"ECBMMT256", []string{"kat", "aes-256-ecb"}, false}, + {"ECBVarKey128", []string{"kat", "aes-128-ecb"}, false}, + {"ECBVarKey192", []string{"kat", "aes-192-ecb"}, false}, + {"ECBVarKey256", []string{"kat", "aes-256-ecb"}, false}, + {"ECBVarTxt128", []string{"kat", "aes-128-ecb"}, false}, + {"ECBVarTxt192", []string{"kat", "aes-192-ecb"}, false}, + {"ECBVarTxt256", []string{"kat", "aes-256-ecb"}, false}, + // AES Monte-Carlo tests + {"ECBMCT128", []string{"mct", "aes-128-ecb"}, false}, + {"ECBMCT192", []string{"mct", "aes-192-ecb"}, false}, + {"ECBMCT256", []string{"mct", "aes-256-ecb"}, false}, + {"CBCMCT128", []string{"mct", "aes-128-cbc"}, false}, + {"CBCMCT192", []string{"mct", "aes-192-cbc"}, false}, + {"CBCMCT256", []string{"mct", "aes-256-cbc"}, false}, + }, +} + +var ecdsa2KeyPairTests = testSuite{ + "ECDSA2", + "ecdsa2_keypair", + nil, + []test{{"KeyPair", nil, true}}, +} + +var ecdsa2PKVTests = testSuite{ + "ECDSA2", + "ecdsa2_pkv", + nil, + []test{{"PKV", nil, false}}, +} + +var ecdsa2SigGenTests = testSuite{ + "ECDSA2", + "ecdsa2_siggen", + nil, + []test{ + {"SigGen", []string{"SigGen"}, true}, + {"SigGenComponent", []string{"SigGenComponent"}, true}, + }, +} + +var ecdsa2SigVerTests = testSuite{ + "ECDSA2", + "ecdsa2_sigver", + nil, + []test{{"SigVer", nil, false}}, +} + +var rsa2KeyGenTests = testSuite{ + "RSA2", + "rsa2_keygen", + nil, + []test{ + {"KeyGen_RandomProbablyPrime3_3", nil, true}, + }, +} + +var rsa2SigGenTests = testSuite{ + "RSA2", + "rsa2_siggen", + nil, + []test{ + {"SigGen15_186-3", []string{"pkcs15"}, true}, + {"SigGenPSS_186-3", []string{"pss"}, true}, + }, +} + +var rsa2SigVerTests = testSuite{ + "RSA2", + "rsa2_sigver", + func(s *bufio.Scanner, state *nextLineState) (string, bool, bool) { + for { + if !s.Scan() { + return "", false, false + } + + line := s.Text() + if strings.HasPrefix(line, "p = ") || strings.HasPrefix(line, "d = ") || strings.HasPrefix(line, "SaltVal = ") || strings.HasPrefix(line, "EM with ") { + continue + } + if strings.HasPrefix(line, "q = ") { + // Skip the "q = " line and an additional blank line. + if !s.Scan() || + len(strings.TrimSpace(s.Text())) > 0 { + return "", false, false + } + continue + } + return line, false, true + } + }, + []test{ + {"SigVer15_186-3", []string{"pkcs15"}, false}, + {"SigVerPSS_186-3", []string{"pss"}, false}, + }, +} + +var hmacTests = testSuite{ + "HMAC", + "hmac", + nil, + []test{{"HMAC", nil, false}}, +} + +var shaTests = testSuite{ + "SHA", + "sha", + nil, + []test{ + {"SHA1LongMsg", []string{"SHA1"}, false}, + {"SHA1ShortMsg", []string{"SHA1"}, false}, + {"SHA224LongMsg", []string{"SHA224"}, false}, + {"SHA224ShortMsg", []string{"SHA224"}, false}, + {"SHA256LongMsg", []string{"SHA256"}, false}, + {"SHA256ShortMsg", []string{"SHA256"}, false}, + {"SHA384LongMsg", []string{"SHA384"}, false}, + {"SHA384ShortMsg", []string{"SHA384"}, false}, + {"SHA512LongMsg", []string{"SHA512"}, false}, + {"SHA512ShortMsg", []string{"SHA512"}, false}, + }, +} + +var shaMonteTests = testSuite{ + "SHA", + "sha_monte", + nil, + []test{ + {"SHA1Monte", []string{"SHA1"}, false}, + {"SHA224Monte", []string{"SHA224"}, false}, + {"SHA256Monte", []string{"SHA256"}, false}, + {"SHA384Monte", []string{"SHA384"}, false}, + {"SHA512Monte", []string{"SHA512"}, false}, + }, +} + +var ctrDRBGTests = testSuite{ + "DRBG800-90A", + "ctr_drbg", + nil, + []test{{"CTR_DRBG", nil, false}}, +} + +var tdesTests = testSuite{ + "TDES", + "tdes", + nil, + []test{ + {"TCBCMMT2", []string{"kat", "des-ede-cbc"}, false}, + {"TCBCMMT3", []string{"kat", "des-ede3-cbc"}, false}, + {"TCBCMonte2", []string{"mct", "des-ede3-cbc"}, false}, + {"TCBCMonte3", []string{"mct", "des-ede3-cbc"}, false}, + {"TCBCinvperm", []string{"kat", "des-ede3-cbc"}, false}, + {"TCBCpermop", []string{"kat", "des-ede3-cbc"}, false}, + {"TCBCsubtab", []string{"kat", "des-ede3-cbc"}, false}, + {"TCBCvarkey", []string{"kat", "des-ede3-cbc"}, false}, + {"TCBCvartext", []string{"kat", "des-ede3-cbc"}, false}, + {"TECBMMT2", []string{"kat", "des-ede"}, false}, + {"TECBMMT3", []string{"kat", "des-ede3"}, false}, + {"TECBMonte2", []string{"mct", "des-ede3"}, false}, + {"TECBMonte3", []string{"mct", "des-ede3"}, false}, + {"TECBinvperm", []string{"kat", "des-ede3"}, false}, + {"TECBpermop", []string{"kat", "des-ede3"}, false}, + {"TECBsubtab", []string{"kat", "des-ede3"}, false}, + {"TECBvarkey", []string{"kat", "des-ede3"}, false}, + {"TECBvartext", []string{"kat", "des-ede3"}, false}, + }, +} + +var keyWrapTests = testSuite{ + "KeyWrap38F", + "keywrap", + nil, + []test{ + {"KW_AD_128", []string{"dec", "128"}, false}, + {"KW_AD_192", []string{"dec", "192"}, false}, + {"KW_AD_256", []string{"dec", "256"}, false}, + {"KW_AE_128", []string{"enc", "128"}, false}, + {"KW_AE_192", []string{"enc", "192"}, false}, + {"KW_AE_256", []string{"enc", "256"}, false}, + {"KWP_AD_128", []string{"dec-pad", "128"}, false}, + {"KWP_AD_192", []string{"dec-pad", "192"}, false}, + {"KWP_AD_256", []string{"dec-pad", "256"}, false}, + {"KWP_AE_128", []string{"enc-pad", "128"}, false}, + {"KWP_AE_192", []string{"enc-pad", "192"}, false}, + {"KWP_AE_256", []string{"enc-pad", "256"}, false}, + }, +} + +var kasTests = testSuite{ + "KAS", + "kas", + func(s *bufio.Scanner, state *nextLineState) (line string, isWildcard, ok bool) { + for { + // If the response file will include the IUT hash next, + // return a wildcard signal because this cannot be + // matched against the FAX file. + if state.nextIsIUTHash { + state.nextIsIUTHash = false + return "", true, true + } + + if !s.Scan() { + return "", false, false + } + + line := s.Text() + if strings.HasPrefix(line, "deCAVS = ") || strings.HasPrefix(line, "Z = ") { + continue + } + if strings.HasPrefix(line, "CAVSHashZZ = ") { + state.nextIsIUTHash = true + } + return line, false, true + } + }, + []test{ + {"KASFunctionTest_ECCEphemeralUnified_NOKC_ZZOnly_init", []string{"function"}, true}, + {"KASFunctionTest_ECCEphemeralUnified_NOKC_ZZOnly_resp", []string{"function"}, true}, + {"KASValidityTest_ECCEphemeralUnified_NOKC_ZZOnly_init", []string{"validity"}, false}, + {"KASValidityTest_ECCEphemeralUnified_NOKC_ZZOnly_resp", []string{"validity"}, false}, + }, +} + +var tlsKDFTests = testSuite{ + "KDF135", + "tlskdf", + nil, + []test{ + {"tls", nil, false}, + }, +} + +var testSuites = []*testSuite{ + &aesGCMTests, + &aesTests, + &ctrDRBGTests, + &ecdsa2KeyPairTests, + &ecdsa2PKVTests, + &ecdsa2SigGenTests, + &ecdsa2SigVerTests, + &hmacTests, + &keyWrapTests, + &rsa2KeyGenTests, + &rsa2SigGenTests, + &rsa2SigVerTests, + &shaTests, + &shaMonteTests, + &tdesTests, + &kasTests, + &tlsKDFTests, +} + +// testInstance represents a specific test in a testSuite. +type testInstance struct { + suite *testSuite + testIndex int +} + +func worker(wg *sync.WaitGroup, work <-chan testInstance) { + defer wg.Done() + + for ti := range work { + test := ti.suite.tests[ti.testIndex] + + if err := doTest(ti.suite, test); err != nil { + fmt.Fprintf(os.Stderr, "%s\n", err) + os.Exit(2) + } + + if !*noFAX && !test.noFAX { + if err := compareFAX(ti.suite, test); err != nil { + fmt.Fprintf(os.Stderr, "%s\n", err) + os.Exit(3) + } + } + } +} + +func checkAndroidPrereqs() error { + // The cavp binary, and a matching libcrypto.so, are required to be placed + // in /data/local/tmp before running this script. + if err := exec.Command("adb", "shell", "ls", androidCAVPPath).Run(); err != nil { + return errors.New("failed to list cavp binary; ensure that adb works and cavp binary is in place: " + err.Error()) + } + if err := exec.Command("adb", "shell", "ls", androidLibCryptoPath).Run(); err != nil { + return errors.New("failed to list libcrypto.so; ensure that library is in place: " + err.Error()) + } + return nil +} + +func main() { + flag.Parse() + + if *android { + if err := checkAndroidPrereqs(); err != nil { + fmt.Fprintf(os.Stderr, "%s\n", err) + os.Exit(1) + } + } else if len(*oraclePath) == 0 { + fmt.Fprintf(os.Stderr, "Must give -oracle-bin\n") + os.Exit(1) + } + + work := make(chan testInstance) + var wg sync.WaitGroup + + numWorkers := runtime.NumCPU() + if *android { + numWorkers = 1 + } + + for i := 0; i < numWorkers; i++ { + wg.Add(1) + go worker(&wg, work) + } + + for _, suite := range testSuites { + for i := range suite.tests { + work <- testInstance{suite, i} + } + } + + close(work) + wg.Wait() +} + +func doTest(suite *testSuite, test test) error { + bin := *oraclePath + var args []string + + if *android { + bin = "adb" + args = []string{"shell", "LD_LIBRARY_PATH=" + androidTmpPath, androidCAVPPath} + } + + args = append(args, suite.suite) + args = append(args, test.args...) + reqPath := filepath.Join(suite.getDirectory(), "req", test.inFile+".req") + var reqPathOnDevice string + + if *android { + reqPathOnDevice = path.Join(androidTmpPath, test.inFile+".req") + if err := exec.Command("adb", "push", reqPath, reqPathOnDevice).Run(); err != nil { + return errors.New("failed to push request file: " + err.Error()) + } + args = append(args, reqPathOnDevice) + } else { + args = append(args, reqPath) + } + + respDir := filepath.Join(suite.getDirectory(), "resp") + if err := os.Mkdir(respDir, 0755); err != nil && !os.IsExist(err) { + return fmt.Errorf("cannot create resp directory: %s", err) + } + outPath := filepath.Join(respDir, test.inFile+".rsp") + outFile, err := os.OpenFile(outPath, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644) + if err != nil { + return fmt.Errorf("cannot open output file for %q %q: %s", suite.getDirectory(), test.inFile, err) + } + defer outFile.Close() + + cmd := exec.Command(bin, args...) + cmd.Stdout = outFile + cmd.Stderr = os.Stderr + + cmdLine := strings.Join(append([]string{bin}, args...), " ") + startTime := time.Now() + if err := cmd.Run(); err != nil { + return fmt.Errorf("cannot run command for %q %q (%s): %s", suite.getDirectory(), test.inFile, cmdLine, err) + } + + fmt.Printf("%s (%ds)\n", cmdLine, int(time.Since(startTime).Seconds())) + + if *android { + exec.Command("adb", "shell", "rm", reqPathOnDevice).Run() + } + + return nil +} + +func canonicalizeLine(in string) string { + if strings.HasPrefix(in, "Result = P (") { + return "Result = P" + } + if strings.HasPrefix(in, "Result = F (") { + return "Result = F" + } + return in +} + +func compareFAX(suite *testSuite, test test) error { + nextLineFunc := suite.nextLineFunc + if nextLineFunc == nil { + nextLineFunc = func(s *bufio.Scanner, state *nextLineState) (string, bool, bool) { + if !s.Scan() { + return "", false, false + } + return s.Text(), false, true + } + } + + respPath := filepath.Join(suite.getDirectory(), "resp", test.inFile+".rsp") + respFile, err := os.Open(respPath) + if err != nil { + return fmt.Errorf("cannot read output of %q %q: %s", suite.getDirectory(), test.inFile, err) + } + defer respFile.Close() + + faxPath := filepath.Join(suite.getDirectory(), "fax", test.inFile+".fax") + faxFile, err := os.Open(faxPath) + if err != nil { + return fmt.Errorf("cannot open fax file for %q %q: %s", suite.getDirectory(), test.inFile, err) + } + defer faxFile.Close() + + respScanner := bufio.NewScanner(respFile) + faxScanner := bufio.NewScanner(faxFile) + var nextLineState nextLineState + + lineNo := 0 + inHeader := true + + for respScanner.Scan() { + lineNo++ + respLine := respScanner.Text() + var faxLine string + var isWildcard, ok bool + + if inHeader && (len(respLine) == 0 || respLine[0] == '#') { + continue + } + + for { + haveFaxLine := false + + if inHeader { + for { + if faxLine, isWildcard, ok = nextLineFunc(faxScanner, &nextLineState); !ok { + break + } + if len(faxLine) != 0 && faxLine[0] != '#' { + haveFaxLine = true + break + } + } + + inHeader = false + } else { + faxLine, isWildcard, haveFaxLine = nextLineFunc(faxScanner, &nextLineState) + } + + if !haveFaxLine { + // Ignore blank lines at the end of the generated file. + if len(respLine) == 0 { + break + } + return fmt.Errorf("resp file is longer than fax for %q %q", suite.getDirectory(), test.inFile) + } + + if strings.HasPrefix(faxLine, " (Reason: ") { + continue + } + + break + } + + if isWildcard || canonicalizeLine(faxLine) == canonicalizeLine(respLine) { + continue + } + + return fmt.Errorf("resp and fax differ at line %d for %q %q: %q vs %q", lineNo, suite.getDirectory(), test.inFile, respLine, faxLine) + } + + if _, _, ok := nextLineFunc(faxScanner, &nextLineState); ok { + return fmt.Errorf("fax file is longer than resp for %q %q", suite.getDirectory(), test.inFile) + } + + return nil +} |