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