Add tool to run CAVP tests.

Change-Id: If503b65de2879186b23ad148363b8ec8be4c611c
Reviewed-on: https://boringssl-review.googlesource.com/15644
Reviewed-by: Martin Kreichgauer <martinkr@google.com>
Reviewed-by: Adam Langley <agl@google.com>
diff --git a/crypto/fipsoracle/run_cavp.go b/crypto/fipsoracle/run_cavp.go
new file mode 100644
index 0000000..88ef35d
--- /dev/null
+++ b/crypto/fipsoracle/run_cavp.go
@@ -0,0 +1,226 @@
+// 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"
+	"flag"
+	"fmt"
+	"os"
+	"os/exec"
+	"path/filepath"
+	"strings"
+)
+
+var (
+	binaryDir = flag.String("bin-dir", "", "Directory containing fipsoracle binaries")
+)
+
+// 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
+}
+
+// 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
+	// binary is the name of the binary that can process these tests.
+	binary    string
+	tests     []test
+}
+
+var aesGCMTests = testSuite{
+	"AES_GCM",
+	"cavp_aes_gcm_test",
+	[]test{
+		{"gcmDecrypt128", []string{"dec", "aes-128-gcm"}, false},
+		{"gcmDecrypt256", []string{"dec", "aes-256-gcm"}, false},
+		{"gcmEncryptIntIV128", []string{"enc", "aes-128-gcm"}, true},
+		{"gcmEncryptIntIV256", []string{"enc", "aes-256-gcm"}, true},
+	},
+}
+
+var aesTests = testSuite{
+	"AES",
+	"cavp_aes_test",
+	[]test{
+		{"CBCGFSbox128", []string{"aes-128-cbc"}, false},
+		{"CBCGFSbox192", []string{"aes-192-cbc"}, false},
+		{"CBCGFSbox256", []string{"aes-256-cbc"}, false},
+		{"CBCKeySbox128", []string{"aes-128-cbc"}, false},
+		{"CBCKeySbox192", []string{"aes-192-cbc"}, false},
+		{"CBCKeySbox256", []string{"aes-256-cbc"}, false},
+		{"CBCMMT128", []string{"aes-128-cbc"}, false},
+		{"CBCMMT192", []string{"aes-192-cbc"}, false},
+		{"CBCMMT256", []string{"aes-256-cbc"}, false},
+		{"CBCVarKey128", []string{"aes-128-cbc"}, false},
+		{"CBCVarKey192", []string{"aes-192-cbc"}, false},
+		{"CBCVarKey256", []string{"aes-256-cbc"}, false},
+		{"CBCVarTxt128", []string{"aes-128-cbc"}, false},
+		{"CBCVarTxt192", []string{"aes-192-cbc"}, false},
+		{"CBCVarTxt256", []string{"aes-256-cbc"}, false},
+		{"ECBGFSbox128", []string{"aes-128-ecb"}, false},
+		{"ECBGFSbox192", []string{"aes-192-ecb"}, false},
+		{"ECBGFSbox256", []string{"aes-256-ecb"}, false},
+		{"ECBKeySbox128", []string{"aes-128-ecb"}, false},
+		{"ECBKeySbox192", []string{"aes-192-ecb"}, false},
+		{"ECBKeySbox256", []string{"aes-256-ecb"}, false},
+		{"ECBMMT128", []string{"aes-128-ecb"}, false},
+		{"ECBMMT192", []string{"aes-192-ecb"}, false},
+		{"ECBMMT256", []string{"aes-256-ecb"}, false},
+		{"ECBVarKey128", []string{"aes-128-ecb"}, false},
+		{"ECBVarKey192", []string{"aes-192-ecb"}, false},
+		{"ECBVarKey256", []string{"aes-256-ecb"}, false},
+		{"ECBVarTxt128", []string{"aes-128-ecb"}, false},
+		{"ECBVarTxt192", []string{"aes-192-ecb"}, false},
+		{"ECBVarTxt256", []string{"aes-256-ecb"}, false},
+	},
+}
+
+// AES Monte-Carlo tests need a different binary.
+//{"ECBMCT128", []string{"aes-128-ecb"}, false},
+//{"ECBMCT192", []string{"aes-192-ecb"}, false},
+//{"ECBMCT256", []string{"aes-256-ecb"}, false},
+//{"CBCMCT128", []string{"aes-128-cbc"}, false},
+//{"CBCMCT192", []string{"aes-192-cbc"}, false},
+//{"CBCMCT256", []string{"aes-256-cbc"}, false},
+
+var allTestSuites = []*testSuite{
+	&aesGCMTests,
+	&aesTests,
+}
+
+func main() {
+	flag.Parse()
+
+	if len(*binaryDir) == 0 {
+		fmt.Fprintf(os.Stderr, "Must give -bin-dir\n")
+		os.Exit(1)
+	}
+
+	for _, suite := range allTestSuites {
+		for _, test := range suite.tests {
+			if err := doTest(suite, test); err != nil {
+				fmt.Fprintf(os.Stderr, "%s\n", err)
+				os.Exit(2)
+			}
+
+			if !test.noFAX {
+				if err := compareFAX(suite, test); err != nil {
+					fmt.Fprintf(os.Stderr, "%s\n", err)
+					os.Exit(3)
+				}
+			}
+		}
+	}
+}
+
+func doTest(suite *testSuite, test test) error {
+	binary := filepath.Join(*binaryDir, suite.binary)
+
+	var args []string
+	args = append(args, test.args...)
+	args = append(args, filepath.Join(suite.directory, "req", test.inFile+".req"))
+
+	outPath := filepath.Join(suite.directory, "resp", test.inFile+".resp")
+	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.directory, test.inFile, err)
+	}
+	defer outFile.Close()
+
+	cmd := exec.Command(binary, args...)
+	cmd.Stdout = outFile
+	cmd.Stderr = os.Stderr
+
+	if err := cmd.Run(); err != nil {
+		return fmt.Errorf("cannot run command for %q %q: %s", suite.directory, test.inFile, err)
+	}
+
+	return nil
+}
+
+func compareFAX(suite *testSuite, test test) error {
+	respPath := filepath.Join(suite.directory, "resp", test.inFile+".resp")
+	respFile, err := os.Open(respPath)
+	if err != nil {
+		return fmt.Errorf("cannot read output of %q %q: %s", suite.directory, test.inFile, err)
+	}
+	defer respFile.Close()
+
+	faxPath := filepath.Join(suite.directory, "fax", test.inFile+".fax")
+	faxFile, err := os.Open(faxPath)
+	if err != nil {
+		return fmt.Errorf("cannot open fax file for %q %q: %s", suite.directory, test.inFile, err)
+	}
+	defer faxFile.Close()
+
+	respScanner := bufio.NewScanner(respFile)
+	faxScanner := bufio.NewScanner(faxFile)
+
+	lineNo := 0
+	inHeader := true
+
+	for respScanner.Scan() {
+		lineNo++
+		respLine := respScanner.Text()
+		var faxLine string
+
+		if inHeader && (len(respLine) == 0 || respLine[0] == '#') {
+			continue
+		}
+
+		for {
+			haveFaxLine := false
+
+			if inHeader {
+				for faxScanner.Scan() {
+					faxLine = faxScanner.Text()
+					if len(faxLine) != 0 && faxLine[0] != '#' {
+						haveFaxLine = true
+						break
+					}
+				}
+
+				inHeader = false
+			} else {
+				if faxScanner.Scan() {
+					faxLine = faxScanner.Text()
+					haveFaxLine = true
+				}
+			}
+
+			if !haveFaxLine {
+				return fmt.Errorf("resp file is longer than fax for %q %q", suite.directory, test.inFile)
+			}
+
+			if strings.HasPrefix(faxLine, " (Reason: ") {
+				continue
+			}
+
+			break
+		}
+
+		if faxLine == respLine {
+			continue
+		}
+
+		return fmt.Errorf("resp and fax differ at line %d for %q %q: %q vs %q", lineNo, suite.directory, test.inFile, respLine, faxLine)
+	}
+
+	if faxScanner.Scan() {
+		return fmt.Errorf("fax file is longer than resp for %q %q", suite.directory, test.inFile)
+	}
+
+	return nil
+}