From 1740ff90a7da10408e6175b516946b392ec8f11f Mon Sep 17 00:00:00 2001 From: Adam Langley Date: Tue, 6 Dec 2022 18:21:50 -0800 Subject: [PATCH] acvptool: add support for uploading results. See the update to ACVP.md for documentation but this now allows running a test to be broken down into separate commands for each step: fetching, processing, and uploading. Change-Id: Id86d1cd0b07fcc9bdc6c665072b511da0832bdde Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/55608 Reviewed-by: David Benjamin Reviewed-by: Adam Langley Commit-Queue: Adam Langley --- util/fipstools/acvp/ACVP.md | 8 ++- util/fipstools/acvp/acvptool/acvp.go | 99 ++++++++++++++++++++++++---- 2 files changed, 95 insertions(+), 12 deletions(-) diff --git a/util/fipstools/acvp/ACVP.md b/util/fipstools/acvp/ACVP.md index d933a9d6b..d3578e206 100644 --- a/util/fipstools/acvp/ACVP.md +++ b/util/fipstools/acvp/ACVP.md @@ -251,4 +251,10 @@ The current list of objects is: In online mode, a given algorithm can be run by using the `-run` option. For example, `-run SHA2-256`. This will fetch a vector set, have the module-under-test answer it, and upload the answer. If you want to just fetch the vector set for later use with the `-json` option (documented above) then you can use `-fetch` instead of `-run`. The `-fetch` option also supports passing `-expected-out ` to fetch and write the expected results, if the server supports that. -The tool doesn't currently support the sorts of operations that a lab would need, like uploading results from a file. +After results have been produced with `-json`, they can be uploaded with `-upload`. So `-run` is effectively these three steps combined: + +``` +./acvptool -fetch SHA2-256 > request +./acvptool -json request > result +./acvptool -upload result +``` diff --git a/util/fipstools/acvp/acvptool/acvp.go b/util/fipstools/acvp/acvptool/acvp.go index ecfe088ef..c294eb8de 100644 --- a/util/fipstools/acvp/acvptool/acvp.go +++ b/util/fipstools/acvp/acvptool/acvp.go @@ -45,6 +45,7 @@ var ( dumpRegcap = flag.Bool("regcap", false, "Print module capabilities JSON to stdout") configFilename = flag.String("config", "config.json", "Location of the configuration JSON file") jsonInputFile = flag.String("json", "", "Location of a vector-set input file") + uploadInputFile = flag.String("upload", "", "Location of a JSON results file to upload") runFlag = flag.String("run", "", "Name of primitive to run tests for") fetchFlag = flag.String("fetch", "", "Name of primitive to fetch vectors for") expectedOutFlag = flag.String("expected-out", "", "Name of a file to write the expected results to") @@ -179,12 +180,12 @@ func trimLeadingSlash(s string) string { return s } -// looksLikeHeaderElement returns true iff element looks like it's a header, not -// a test. Some ACVP files contain a header as the first element that should be -// duplicated into the response, and some don't. If the element contains -// a "url" field, or if it's missing an "algorithm" field, then we guess that -// it's a header. -func looksLikeHeaderElement(element json.RawMessage) bool { +// looksLikeVectorSetHeader returns true iff element looks like it's a +// vectorSetHeader, not a test. Some ACVP files contain a header as the first +// element that should be duplicated into the response, and some don't. If the +// element contains a "url" field, or if it's missing an "algorithm" field, +// then we guess that it's a header. +func looksLikeVectorSetHeader(element json.RawMessage) bool { var headerFields struct { URL string `json:"url"` Algorithm string `json:"algorithm"` @@ -214,7 +215,7 @@ func processFile(filename string, supportedAlgos []map[string]interface{}, middl } var header json.RawMessage - if looksLikeHeaderElement(elements[0]) { + if looksLikeVectorSetHeader(elements[0]) { header, elements = elements[0], elements[1:] if len(elements) == 0 { return errors.New("JSON input is empty") @@ -269,6 +270,7 @@ func processFile(filename string, supportedAlgos []map[string]interface{}, middl group := map[string]interface{}{ "vsId": commonFields.ID, "testGroups": replyGroups, + "algorithm": algo, } replyBytes, err := json.MarshalIndent(group, "", " ") if err != nil { @@ -454,6 +456,76 @@ FetchResults: } } +// vectorSetHeader is the first element in the array of JSON elements that makes +// up the on-disk format for a vector set. +type vectorSetHeader struct { + URL string `json:"url,omitempty"` + VectorSetURLs []string `json:"vectorSetUrls,omitempty"` + Time string `json:"time,omitempty"` +} + +func uploadFromFile(file string, config *Config, sessionTokensCacheDir string) { + if len(*jsonInputFile) > 0 { + log.Fatalf("-upload cannot be used with -json") + } + if len(*runFlag) > 0 { + log.Fatalf("-upload cannot be used with -run") + } + if len(*fetchFlag) > 0 { + log.Fatalf("-upload cannot be used with -fetch") + } + if len(*expectedOutFlag) > 0 { + log.Fatalf("-upload cannot be used with -expected-out") + } + if *dumpRegcap { + log.Fatalf("-upload cannot be used with -regcap") + } + + in, err := os.Open(file) + if err != nil { + log.Fatalf("Cannot open input: %s", err) + } + defer in.Close() + + decoder := json.NewDecoder(in) + + var input []json.RawMessage + if err := decoder.Decode(&input); err != nil { + log.Fatalf("Failed to parse input: %s", err) + } + + if len(input) < 2 { + log.Fatalf("Input JSON has fewer than two elements") + } + + var header vectorSetHeader + if err := json.Unmarshal(input[0], &header); err != nil { + log.Fatalf("Failed to parse input header: %s", err) + } + + if numGroups := len(input) - 1; numGroups != len(header.VectorSetURLs) { + log.Fatalf("have %d URLs from header, but only %d result groups", len(header.VectorSetURLs), numGroups) + } + + server, err := connect(config, sessionTokensCacheDir) + if err != nil { + log.Fatal(err) + } + + for i, url := range header.VectorSetURLs { + log.Printf("Uploading result for %q", url) + if err := uploadResult(server, url, input[i+1]); err != nil { + log.Fatalf("Failed to upload: %s", err) + } + } + + if ok, err := getResultsWithRetry(server, header.URL); err != nil { + log.Fatal(err) + } else if !ok { + os.Exit(1) + } +} + func main() { flag.Parse() @@ -474,6 +546,11 @@ func main() { } } + if len(*uploadInputFile) > 0 { + uploadFromFile(*uploadInputFile, &config, sessionTokensCacheDir) + return + } + middle, err := subprocess.New(*wrapperPath) if err != nil { log.Fatalf("failed to initialise middle: %s", err) @@ -629,10 +706,10 @@ func main() { if len(*fetchFlag) > 0 { io.WriteString(fetchOutputTee, "[\n") - json.NewEncoder(fetchOutputTee).Encode(map[string]interface{}{ - "url": url, - "vectorSetUrls": result.VectorSetURLs, - "time": time.Now().Format(time.RFC3339), + json.NewEncoder(fetchOutputTee).Encode(vectorSetHeader{ + URL: url, + VectorSetURLs: result.VectorSetURLs, + Time: time.Now().Format(time.RFC3339), }) }