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