blob: e5c67c0cf2105eed9bd0a35cb3a7273dc1a127f6 [file] [log] [blame]
// 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"
"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")
)
// 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
// suite names the test suite to pass as the first command-line argument.
suite string
// faxScanFunc, if not nil, is the function to use instead of
// (*bufio.Scanner).Scan. This can be used to skip lines.
faxScanFunc func(*bufio.Scanner) 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) bool {
for {
if !s.Scan() {
return 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() {
return false
}
if len(strings.TrimSpace(s.Text())) > 0 {
return false
}
continue
}
return 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_256", []string{"dec", "256"}, false},
{"KW_AE_128", []string{"enc", "128"}, false},
{"KW_AE_256", []string{"enc", "256"}, false},
},
}
var allTestSuites = []*testSuite{
&aesGCMTests,
&aesTests,
&ctrDRBGTests,
&ecdsa2KeyPairTests,
&ecdsa2PKVTests,
&ecdsa2SigGenTests,
&ecdsa2SigVerTests,
&hmacTests,
&keyWrapTests,
&rsa2KeyGenTests,
&rsa2SigGenTests,
&rsa2SigVerTests,
&shaTests,
&shaMonteTests,
&tdesTests,
}
// 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 main() {
flag.Parse()
if len(*oraclePath) == 0 {
fmt.Fprintf(os.Stderr, "Must give -oracle-bin\n")
os.Exit(1)
}
work := make(chan testInstance)
var wg sync.WaitGroup
for i := 0; i < runtime.NumCPU(); i++ {
wg.Add(1)
go worker(&wg, work)
}
for _, suite := range allTestSuites {
for i := range suite.tests {
work <- testInstance{suite, i}
}
}
close(work)
wg.Wait()
}
func doTest(suite *testSuite, test test) error {
args := []string{suite.suite}
args = append(args, test.args...)
args = append(args, filepath.Join(suite.getDirectory(), "req", test.inFile+".req"))
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(*oraclePath, args...)
cmd.Stdout = outFile
cmd.Stderr = os.Stderr
cmdLine := strings.Join(append([]string{*oraclePath}, 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()))
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 {
faxScanFunc := suite.faxScanFunc
if faxScanFunc == nil {
faxScanFunc = (*bufio.Scanner).Scan
}
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)
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 faxScanFunc(faxScanner) {
faxLine = faxScanner.Text()
if len(faxLine) != 0 && faxLine[0] != '#' {
haveFaxLine = true
break
}
}
inHeader = false
} else {
if faxScanFunc(faxScanner) {
faxLine = faxScanner.Text()
haveFaxLine = true
}
}
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 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 faxScanFunc(faxScanner) {
return fmt.Errorf("fax file is longer than resp for %q %q", suite.getDirectory(), test.inFile)
}
return nil
}