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 <davidben@google.com>
Reviewed-by: Adam Langley <agl@google.com>
Commit-Queue: Adam Langley <agl@google.com>
fips-20230428
Adam Langley 2 years ago committed by Boringssl LUCI CQ
parent d77fdbff01
commit 1740ff90a7
  1. 8
      util/fipstools/acvp/ACVP.md
  2. 99
      util/fipstools/acvp/acvptool/acvp.go

@ -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 <filename>` to fetch and write the expected results, if the server supports that. 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 <filename>` 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
```

@ -45,6 +45,7 @@ var (
dumpRegcap = flag.Bool("regcap", false, "Print module capabilities JSON to stdout") dumpRegcap = flag.Bool("regcap", false, "Print module capabilities JSON to stdout")
configFilename = flag.String("config", "config.json", "Location of the configuration JSON file") configFilename = flag.String("config", "config.json", "Location of the configuration JSON file")
jsonInputFile = flag.String("json", "", "Location of a vector-set input 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") runFlag = flag.String("run", "", "Name of primitive to run tests for")
fetchFlag = flag.String("fetch", "", "Name of primitive to fetch vectors 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") 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 return s
} }
// looksLikeHeaderElement returns true iff element looks like it's a header, not // looksLikeVectorSetHeader returns true iff element looks like it's a
// a test. Some ACVP files contain a header as the first element that should be // vectorSetHeader, not a test. Some ACVP files contain a header as the first
// duplicated into the response, and some don't. If the element contains // element that should be duplicated into the response, and some don't. If the
// a "url" field, or if it's missing an "algorithm" field, then we guess that // element contains a "url" field, or if it's missing an "algorithm" field,
// it's a header. // then we guess that it's a header.
func looksLikeHeaderElement(element json.RawMessage) bool { func looksLikeVectorSetHeader(element json.RawMessage) bool {
var headerFields struct { var headerFields struct {
URL string `json:"url"` URL string `json:"url"`
Algorithm string `json:"algorithm"` Algorithm string `json:"algorithm"`
@ -214,7 +215,7 @@ func processFile(filename string, supportedAlgos []map[string]interface{}, middl
} }
var header json.RawMessage var header json.RawMessage
if looksLikeHeaderElement(elements[0]) { if looksLikeVectorSetHeader(elements[0]) {
header, elements = elements[0], elements[1:] header, elements = elements[0], elements[1:]
if len(elements) == 0 { if len(elements) == 0 {
return errors.New("JSON input is empty") return errors.New("JSON input is empty")
@ -269,6 +270,7 @@ func processFile(filename string, supportedAlgos []map[string]interface{}, middl
group := map[string]interface{}{ group := map[string]interface{}{
"vsId": commonFields.ID, "vsId": commonFields.ID,
"testGroups": replyGroups, "testGroups": replyGroups,
"algorithm": algo,
} }
replyBytes, err := json.MarshalIndent(group, "", " ") replyBytes, err := json.MarshalIndent(group, "", " ")
if err != nil { 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() { func main() {
flag.Parse() flag.Parse()
@ -474,6 +546,11 @@ func main() {
} }
} }
if len(*uploadInputFile) > 0 {
uploadFromFile(*uploadInputFile, &config, sessionTokensCacheDir)
return
}
middle, err := subprocess.New(*wrapperPath) middle, err := subprocess.New(*wrapperPath)
if err != nil { if err != nil {
log.Fatalf("failed to initialise middle: %s", err) log.Fatalf("failed to initialise middle: %s", err)
@ -629,10 +706,10 @@ func main() {
if len(*fetchFlag) > 0 { if len(*fetchFlag) > 0 {
io.WriteString(fetchOutputTee, "[\n") io.WriteString(fetchOutputTee, "[\n")
json.NewEncoder(fetchOutputTee).Encode(map[string]interface{}{ json.NewEncoder(fetchOutputTee).Encode(vectorSetHeader{
"url": url, URL: url,
"vectorSetUrls": result.VectorSetURLs, VectorSetURLs: result.VectorSetURLs,
"time": time.Now().Format(time.RFC3339), Time: time.Now().Format(time.RFC3339),
}) })
} }

Loading…
Cancel
Save