1package main 2 3import ( 4 "bytes" 5 "encoding/json" 6 "errors" 7 "fmt" 8 "io" 9 "io/ioutil" 10 neturl "net/url" 11 "os" 12 "os/exec" 13 "os/signal" 14 "path/filepath" 15 "reflect" 16 "strconv" 17 "strings" 18 "syscall" 19 20 "boringssl.googlesource.com/boringssl/util/fipstools/acvp/acvptool/acvp" 21 "golang.org/x/crypto/ssh/terminal" 22) 23 24func updateTerminalSize(term *terminal.Terminal) { 25 width, height, err := terminal.GetSize(0) 26 if err != nil { 27 return 28 } 29 term.SetSize(width, height) 30} 31 32func skipWS(node *node32) *node32 { 33 for ; node != nil && node.pegRule == ruleWS; node = node.next { 34 } 35 return node 36} 37 38func assertNodeType(node *node32, rule pegRule) { 39 if node.pegRule != rule { 40 panic(fmt.Sprintf("expected %q, found %q", rul3s[rule], rul3s[node.pegRule])) 41 } 42} 43 44type Object interface { 45 String() (string, error) 46 Index(string) (Object, error) 47 Search(acvp.Query) (Object, error) 48 Action(action string, args []string) error 49} 50 51type ServerObjectSet struct { 52 env *Env 53 name string 54 searchKeys map[string][]acvp.Relation 55 resultType reflect.Type 56 subObjects map[string]func(*Env, string) (Object, error) 57 canEnumerate bool 58} 59 60func (set ServerObjectSet) String() (string, error) { 61 if !set.canEnumerate { 62 return "[object set " + set.name + "]", nil 63 } 64 65 data := reflect.New(reflect.SliceOf(set.resultType)).Interface() 66 if err := set.env.server.GetPaged(data, "acvp/v1/"+set.name, nil); err != nil { 67 return "", err 68 } 69 ret, err := json.MarshalIndent(data, "", " ") 70 return string(ret), err 71} 72 73func (set ServerObjectSet) Index(indexStr string) (Object, error) { 74 index, err := strconv.ParseUint(indexStr, 0, 64) 75 if err != nil { 76 return nil, fmt.Errorf("object set indexes must be unsigned integers, trying to parse %q failed: %s", indexStr, err) 77 } 78 return ServerObject{&set, index}, nil 79} 80 81func (set ServerObjectSet) Search(condition acvp.Query) (Object, error) { 82 if set.searchKeys == nil { 83 return nil, errors.New("this object set cannot be searched") 84 } 85 86 for _, conj := range condition { 87 NextCondition: 88 for _, cond := range conj { 89 allowed, ok := set.searchKeys[cond.Param] 90 if !ok { 91 return nil, fmt.Errorf("search key %q not valid for this object set", cond.Param) 92 } 93 94 for _, rel := range allowed { 95 if rel == cond.Relation { 96 continue NextCondition 97 } 98 } 99 100 return nil, fmt.Errorf("search key %q cannot be used with relation %q", cond.Param, cond.Relation.String()) 101 } 102 } 103 104 return Search{ServerObjectSet: set, query: condition}, nil 105} 106 107func (set ServerObjectSet) Action(action string, args []string) error { 108 switch action { 109 default: 110 return fmt.Errorf("unknown action %q", action) 111 112 case "new": 113 if len(args) != 0 { 114 return fmt.Errorf("found %d arguments but %q takes none", len(args), action) 115 } 116 117 newContents, err := edit("") 118 if err != nil { 119 return err 120 } 121 122 if strings.TrimSpace(string(newContents)) == "" { 123 io.WriteString(set.env.term, "Resulting file was empty. Ignoring.\n") 124 return nil 125 } 126 127 var result map[string]interface{} 128 if err := set.env.server.Post(&result, "acvp/v1/"+set.name, newContents); err != nil { 129 return err 130 } 131 132 // In case it's a testSession that was just created, poke any access token 133 // into the server's lookup table and the cache. 134 if urlInterface, ok := result["url"]; ok { 135 if url, ok := urlInterface.(string); ok { 136 if tokenInterface, ok := result["accessToken"]; ok { 137 if token, ok := tokenInterface.(string); ok { 138 for strings.HasPrefix(url, "/") { 139 url = url[1:] 140 } 141 set.env.server.PrefixTokens[url] = token 142 if len(set.env.config.SessionTokensCache) > 0 { 143 ioutil.WriteFile(filepath.Join(set.env.config.SessionTokensCache, neturl.PathEscape(url))+".token", []byte(token), 0600) 144 } 145 } 146 } 147 } 148 } 149 150 ret, err := json.MarshalIndent(result, "", " ") 151 if err != nil { 152 return err 153 } 154 set.env.term.Write(ret) 155 return nil 156 } 157} 158 159type ServerObject struct { 160 set *ServerObjectSet 161 index uint64 162} 163 164func (obj ServerObject) String() (string, error) { 165 data := reflect.New(obj.set.resultType).Interface() 166 if err := obj.set.env.server.Get(data, "acvp/v1/"+obj.set.name+"/"+strconv.FormatUint(obj.index, 10)); err != nil { 167 return "", err 168 } 169 ret, err := json.MarshalIndent(data, "", " ") 170 return string(ret), err 171} 172 173func (obj ServerObject) Index(index string) (Object, error) { 174 if obj.set.subObjects == nil { 175 return nil, errors.New("cannot index " + obj.set.name + " objects") 176 } 177 constr, ok := obj.set.subObjects[index] 178 if !ok { 179 return nil, fmt.Errorf("no such subobject %q", index) 180 } 181 return constr(obj.set.env, fmt.Sprintf("%s/%d", obj.set.name, obj.index)) 182} 183 184func (ServerObject) Search(condition acvp.Query) (Object, error) { 185 return nil, errors.New("cannot search individual object") 186} 187 188func edit(initialContents string) ([]byte, error) { 189 tmp, err := ioutil.TempFile("", "acvp*.json") 190 if err != nil { 191 return nil, err 192 } 193 path := tmp.Name() 194 defer os.Remove(path) 195 196 _, err = io.WriteString(tmp, initialContents) 197 tmp.Close() 198 if err != nil { 199 return nil, err 200 } 201 202 editor := os.Getenv("EDITOR") 203 if len(editor) == 0 { 204 editor = "vim" 205 } 206 207 cmd := exec.Command(editor, path) 208 cmd.Stdout = os.Stdout 209 cmd.Stdin = os.Stdin 210 cmd.Stderr = os.Stderr 211 if err := cmd.Run(); err != nil { 212 return nil, err 213 } 214 215 return ioutil.ReadFile(path) 216} 217 218func (obj ServerObject) Action(action string, args []string) error { 219 switch action { 220 default: 221 return fmt.Errorf("unknown action %q", action) 222 223 case "edit": 224 if len(args) != 0 { 225 return fmt.Errorf("found %d arguments but %q takes none", len(args), action) 226 } 227 228 contents, err := obj.String() 229 if err != nil { 230 return err 231 } 232 233 newContents, err := edit(contents) 234 if err != nil { 235 return err 236 } 237 238 if trimmed := strings.TrimSpace(string(newContents)); len(trimmed) == 0 || trimmed == strings.TrimSpace(contents) { 239 io.WriteString(obj.set.env.term, "Resulting file was equal or empty. Not updating.\n") 240 return nil 241 } 242 243 var status acvp.RequestStatus 244 if err := obj.set.env.server.Put(&status, "acvp/v1/"+obj.set.name+"/"+strconv.FormatUint(obj.index, 10), newContents); err != nil { 245 return err 246 } 247 248 fmt.Fprintf(obj.set.env.term, "%#v\n", status) 249 return nil 250 251 case "delete": 252 if len(args) != 0 { 253 return fmt.Errorf("found %d arguments but %q takes none", len(args), action) 254 } 255 return obj.set.env.server.Delete("acvp/v1/" + obj.set.name + "/" + strconv.FormatUint(obj.index, 10)) 256 } 257} 258 259type Search struct { 260 ServerObjectSet 261 query acvp.Query 262} 263 264func (search Search) String() (string, error) { 265 data := reflect.New(reflect.SliceOf(search.resultType)).Interface() 266 fmt.Printf("Searching for %#v\n", search.query) 267 if err := search.env.server.GetPaged(data, "acvp/v1/"+search.name, search.query); err != nil { 268 return "", err 269 } 270 ret, err := json.MarshalIndent(data, "", " ") 271 return string(ret), err 272} 273 274func (search Search) Index(_ string) (Object, error) { 275 return nil, errors.New("indexing of search results not supported") 276} 277 278func (search Search) Search(condition acvp.Query) (Object, error) { 279 search.query = append(search.query, condition...) 280 return search, nil 281} 282 283func (Search) Action(_ string, _ []string) error { 284 return errors.New("no actions supported on search objects") 285} 286 287type Algorithms struct { 288 ServerObjectSet 289} 290 291func (algos Algorithms) String() (string, error) { 292 var result struct { 293 Algorithms []map[string]interface{} `json:"algorithms"` 294 } 295 if err := algos.env.server.Get(&result, "acvp/v1/algorithms"); err != nil { 296 return "", err 297 } 298 ret, err := json.MarshalIndent(result.Algorithms, "", " ") 299 return string(ret), err 300} 301 302type Env struct { 303 line string 304 variables map[string]Object 305 server *acvp.Server 306 term *terminal.Terminal 307 config Config 308} 309 310func (e *Env) bytes(node *node32) []byte { 311 return []byte(e.line[node.begin:node.end]) 312} 313 314func (e *Env) contents(node *node32) string { 315 return e.line[node.begin:node.end] 316} 317 318type stringLiteral struct { 319 env *Env 320 contents string 321} 322 323func (s stringLiteral) String() (string, error) { 324 return s.contents, nil 325} 326 327func (stringLiteral) Index(_ string) (Object, error) { 328 return nil, errors.New("cannot index strings") 329} 330 331func (stringLiteral) Search(_ acvp.Query) (Object, error) { 332 return nil, errors.New("cannot search strings") 333} 334 335func (s stringLiteral) Action(action string, args []string) error { 336 switch action { 337 default: 338 return fmt.Errorf("action %q not supported on string literals", action) 339 340 case "GET": 341 if len(args) != 0 { 342 return fmt.Errorf("found %d arguments but %q takes none", len(args), action) 343 } 344 345 var results map[string]interface{} 346 if err := s.env.server.Get(&results, s.contents); err != nil { 347 return err 348 } 349 ret, err := json.MarshalIndent(results, "", " ") 350 if err != nil { 351 return err 352 } 353 s.env.term.Write(ret) 354 return nil 355 } 356} 357 358type results struct { 359 env *Env 360 prefix string 361} 362 363func (r results) String() (string, error) { 364 var results map[string]interface{} 365 if err := r.env.server.Get(&results, "acvp/v1/"+r.prefix+"/results"); err != nil { 366 return "", err 367 } 368 ret, err := json.MarshalIndent(results, "", " ") 369 return string(ret), err 370} 371 372func (results) Index(_ string) (Object, error) { 373 return nil, errors.New("cannot index results objects") 374} 375 376func (results) Search(_ acvp.Query) (Object, error) { 377 return nil, errors.New("cannot search results objects") 378} 379 380func (results) Action(_ string, _ []string) error { 381 return errors.New("no actions supported on results objects") 382} 383 384func (e *Env) parseStringLiteral(node *node32) string { 385 assertNodeType(node, ruleStringLiteral) 386 in := e.bytes(node) 387 var buf bytes.Buffer 388 for i := 1; i < len(in)-1; i++ { 389 if in[i] == '\\' { 390 switch in[i+1] { 391 case '\\': 392 buf.WriteByte('\\') 393 case 'n': 394 buf.WriteByte('\n') 395 case '"': 396 buf.WriteByte('"') 397 default: 398 panic("unknown escape") 399 } 400 i++ 401 continue 402 } 403 buf.WriteByte(in[i]) 404 } 405 406 return buf.String() 407} 408 409func (e *Env) evalExpression(node *node32) (obj Object, err error) { 410 switch node.pegRule { 411 case ruleStringLiteral: 412 return stringLiteral{e, e.parseStringLiteral(node)}, nil 413 414 case ruleVariable: 415 varName := e.contents(node) 416 obj, ok := e.variables[varName] 417 if !ok { 418 return nil, fmt.Errorf("unknown variable %q", varName) 419 } 420 return obj, nil 421 422 case ruleIndexing: 423 node = node.up 424 assertNodeType(node, ruleVariable) 425 varName := e.contents(node) 426 obj, ok := e.variables[varName] 427 if !ok { 428 return nil, fmt.Errorf("unknown variable %q", varName) 429 } 430 431 node = node.next 432 for node != nil { 433 assertNodeType(node, ruleIndex) 434 indexStr := e.contents(node) 435 if obj, err = obj.Index(indexStr); err != nil { 436 return nil, err 437 } 438 node = node.next 439 } 440 441 return obj, nil 442 443 case ruleSearch: 444 node = node.up 445 assertNodeType(node, ruleVariable) 446 varName := e.contents(node) 447 obj, ok := e.variables[varName] 448 if !ok { 449 return nil, fmt.Errorf("unknown variable %q", varName) 450 } 451 452 node = skipWS(node.next) 453 assertNodeType(node, ruleQuery) 454 node = node.up 455 456 var query acvp.Query 457 for node != nil { 458 assertNodeType(node, ruleConjunctions) 459 query = append(query, e.parseConjunction(node.up)) 460 node = skipWS(node.next) 461 } 462 463 if len(query) == 0 { 464 return nil, errors.New("cannot have empty query") 465 } 466 467 return obj.Search(query) 468 } 469 470 panic("unhandled") 471} 472 473func (e *Env) evalAction(node *node32) error { 474 assertNodeType(node, ruleExpression) 475 obj, err := e.evalExpression(node.up) 476 if err != nil { 477 return err 478 } 479 480 node = node.next 481 assertNodeType(node, ruleCommand) 482 node = node.up 483 assertNodeType(node, ruleFunction) 484 function := e.contents(node) 485 node = node.next 486 487 var args []string 488 for node != nil { 489 assertNodeType(node, ruleArgs) 490 node = node.up 491 args = append(args, e.parseStringLiteral(node)) 492 493 node = skipWS(node.next) 494 } 495 496 return obj.Action(function, args) 497} 498 499func (e *Env) parseConjunction(node *node32) (ret acvp.Conjunction) { 500 for node != nil { 501 assertNodeType(node, ruleConjunction) 502 ret = append(ret, e.parseCondition(node.up)) 503 504 node = skipWS(node.next) 505 if node != nil { 506 assertNodeType(node, ruleConjunctions) 507 node = node.up 508 } 509 } 510 return ret 511} 512 513func (e *Env) parseCondition(node *node32) (ret acvp.Condition) { 514 assertNodeType(node, ruleField) 515 ret.Param = e.contents(node) 516 node = skipWS(node.next) 517 518 assertNodeType(node, ruleRelation) 519 switch e.contents(node) { 520 case "==": 521 ret.Relation = acvp.Equals 522 case "!=": 523 ret.Relation = acvp.NotEquals 524 case "contains": 525 ret.Relation = acvp.Contains 526 case "startsWith": 527 ret.Relation = acvp.StartsWith 528 case "endsWith": 529 ret.Relation = acvp.EndsWith 530 default: 531 panic("relation not handled: " + e.contents(node)) 532 } 533 node = skipWS(node.next) 534 535 ret.Value = e.parseStringLiteral(node) 536 537 return ret 538} 539 540func runInteractive(server *acvp.Server, config Config) { 541 oldState, err := terminal.MakeRaw(0) 542 if err != nil { 543 panic(err) 544 } 545 defer terminal.Restore(0, oldState) 546 term := terminal.NewTerminal(os.Stdin, "> ") 547 548 resizeChan := make(chan os.Signal) 549 go func() { 550 for _ = range resizeChan { 551 updateTerminalSize(term) 552 } 553 }() 554 signal.Notify(resizeChan, syscall.SIGWINCH) 555 556 env := &Env{variables: make(map[string]Object), server: server, term: term, config: config} 557 env.variables["requests"] = ServerObjectSet{ 558 env: env, 559 name: "requests", 560 resultType: reflect.TypeOf(&acvp.RequestStatus{}), 561 canEnumerate: true, 562 } 563 // https://usnistgov.github.io/ACVP/artifacts/draft-fussell-acvp-spec-00.html#rfc.section.11.8 564 env.variables["vendors"] = ServerObjectSet{ 565 env: env, 566 name: "vendors", 567 searchKeys: map[string][]acvp.Relation{ 568 // https://usnistgov.github.io/ACVP/artifacts/draft-fussell-acvp-spec-00.html#rfc.section.11.8.1 569 "name": []acvp.Relation{acvp.Equals, acvp.StartsWith, acvp.EndsWith, acvp.Contains}, 570 "website": []acvp.Relation{acvp.Equals, acvp.StartsWith, acvp.EndsWith, acvp.Contains}, 571 "email": []acvp.Relation{acvp.Equals, acvp.StartsWith, acvp.EndsWith, acvp.Contains}, 572 "phoneNumber": []acvp.Relation{acvp.Equals, acvp.StartsWith, acvp.EndsWith, acvp.Contains}, 573 }, 574 subObjects: map[string]func(*Env, string) (Object, error){ 575 "contacts": func(env *Env, prefix string) (Object, error) { 576 return ServerObjectSet{ 577 env: env, 578 name: prefix + "/contacts", 579 resultType: reflect.TypeOf(&acvp.Person{}), 580 canEnumerate: true, 581 }, nil 582 }, 583 "addresses": func(env *Env, prefix string) (Object, error) { 584 return ServerObjectSet{ 585 env: env, 586 name: prefix + "/addresses", 587 resultType: reflect.TypeOf(&acvp.Address{}), 588 canEnumerate: true, 589 }, nil 590 }, 591 }, 592 resultType: reflect.TypeOf(&acvp.Vendor{}), 593 } 594 // https://usnistgov.github.io/ACVP/artifacts/draft-fussell-acvp-spec-00.html#rfc.section.11.9 595 env.variables["persons"] = ServerObjectSet{ 596 env: env, 597 name: "persons", 598 searchKeys: map[string][]acvp.Relation{ 599 // https://usnistgov.github.io/ACVP/artifacts/draft-fussell-acvp-spec-00.html#rfc.section.11.10.1 600 "fullName": []acvp.Relation{acvp.Equals, acvp.StartsWith, acvp.EndsWith, acvp.Contains}, 601 "email": []acvp.Relation{acvp.Equals, acvp.StartsWith, acvp.EndsWith, acvp.Contains}, 602 "phoneNumber": []acvp.Relation{acvp.Equals, acvp.StartsWith, acvp.EndsWith, acvp.Contains}, 603 "vendorId": []acvp.Relation{acvp.Equals, acvp.NotEquals, acvp.LessThan, acvp.LessThanEqual, acvp.GreaterThan, acvp.GreaterThanEqual}, 604 }, 605 resultType: reflect.TypeOf(&acvp.Person{}), 606 } 607 // https://usnistgov.github.io/ACVP/artifacts/draft-fussell-acvp-spec-00.html#rfc.section.11.11 608 env.variables["modules"] = ServerObjectSet{ 609 env: env, 610 name: "modules", 611 searchKeys: map[string][]acvp.Relation{ 612 // https://usnistgov.github.io/ACVP/artifacts/draft-fussell-acvp-spec-00.html#rfc.section.11.10.1 613 "name": []acvp.Relation{acvp.Equals, acvp.StartsWith, acvp.EndsWith, acvp.Contains}, 614 "version": []acvp.Relation{acvp.Equals, acvp.StartsWith, acvp.EndsWith, acvp.Contains}, 615 "website": []acvp.Relation{acvp.Equals, acvp.StartsWith, acvp.EndsWith, acvp.Contains}, 616 "description": []acvp.Relation{acvp.Equals, acvp.StartsWith, acvp.EndsWith, acvp.Contains}, 617 "type": []acvp.Relation{acvp.Equals, acvp.NotEquals}, 618 "vendorId": []acvp.Relation{acvp.Equals, acvp.NotEquals, acvp.LessThan, acvp.LessThanEqual, acvp.GreaterThan, acvp.GreaterThanEqual}, 619 }, 620 resultType: reflect.TypeOf(&acvp.Module{}), 621 } 622 // https://usnistgov.github.io/ACVP/artifacts/draft-fussell-acvp-spec-00.html#rfc.section.11.12 623 env.variables["oes"] = ServerObjectSet{ 624 env: env, 625 name: "oes", 626 searchKeys: map[string][]acvp.Relation{ 627 // https://usnistgov.github.io/ACVP/artifacts/draft-fussell-acvp-spec-00.html#rfc.section.11.12.1 628 "name": []acvp.Relation{acvp.Equals, acvp.StartsWith, acvp.EndsWith, acvp.Contains}, 629 }, 630 resultType: reflect.TypeOf(&acvp.OperationalEnvironment{}), 631 } 632 // https://usnistgov.github.io/ACVP/artifacts/draft-fussell-acvp-spec-00.html#rfc.section.11.13 633 env.variables["deps"] = ServerObjectSet{ 634 env: env, 635 name: "dependencies", 636 searchKeys: map[string][]acvp.Relation{ 637 // https://usnistgov.github.io/ACVP/artifacts/draft-fussell-acvp-spec-00.html#rfc.section.11.12.1 638 "name": []acvp.Relation{acvp.Equals, acvp.StartsWith, acvp.EndsWith, acvp.Contains}, 639 "type": []acvp.Relation{acvp.Equals, acvp.StartsWith, acvp.EndsWith, acvp.Contains}, 640 "description": []acvp.Relation{acvp.Equals, acvp.StartsWith, acvp.EndsWith, acvp.Contains}, 641 }, 642 resultType: reflect.TypeOf(&acvp.Dependency{}), 643 } 644 // https://usnistgov.github.io/ACVP/artifacts/draft-fussell-acvp-spec-00.html#rfc.section.11.14 645 env.variables["algos"] = Algorithms{ 646 ServerObjectSet{ 647 env: env, 648 name: "algorithms", 649 resultType: reflect.TypeOf(&acvp.Algorithm{}), 650 canEnumerate: true, 651 }, 652 } 653 // https://usnistgov.github.io/ACVP/artifacts/draft-fussell-acvp-spec-00.html#rfc.section.11.15 654 env.variables["sessions"] = ServerObjectSet{ 655 env: env, 656 name: "testSessions", 657 resultType: reflect.TypeOf(&acvp.TestSession{}), 658 canEnumerate: true, 659 subObjects: map[string]func(env *Env, prefix string) (Object, error){ 660 "results": func(env *Env, prefix string) (Object, error) { 661 return results{env: env, prefix: prefix}, nil 662 }, 663 }, 664 } 665 666 for { 667 if env.line, err = term.ReadLine(); err != nil { 668 return 669 } 670 if len(env.line) == 0 { 671 continue 672 } 673 674 stmt := Statement{Buffer: env.line, Pretty: true} 675 stmt.Init() 676 if err := stmt.Parse(); err != nil { 677 io.WriteString(term, err.Error()) 678 continue 679 } 680 681 node := skipWS(stmt.AST().up) 682 switch node.pegRule { 683 case ruleExpression: 684 obj, err := env.evalExpression(node.up) 685 var repr string 686 if err == nil { 687 repr, err = obj.String() 688 } 689 690 if err != nil { 691 fmt.Fprintf(term, "error while evaluating expression: %s\n", err) 692 } else { 693 io.WriteString(term, repr) 694 io.WriteString(term, "\n") 695 } 696 697 case ruleAction: 698 if err := env.evalAction(node.up); err != nil { 699 io.WriteString(term, err.Error()) 700 io.WriteString(term, "\n") 701 } 702 703 default: 704 fmt.Fprintf(term, "internal error parsing input.\n") 705 } 706 } 707} 708