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