// Copyright (c) 2017, Google Inc. // // Permission to use, copy, modify, and/or distribute this software for any // purpose with or without fee is hereby granted, provided that the above // copyright notice and this permission notice appear in all copies. // // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY // SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION // OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN // CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. // The make_cavp utility generates cipher_test input files from NIST CAVP Known // Answer Test response (.rsp) files. package main import ( "bufio" "flag" "fmt" "io" "log" "os" "strings" ) var ( cipher = flag.String("cipher", "", "The name of the cipher/mode (supported: aes, tdes, gcm). Required.") cmdLineLabelStr = flag.String("extra-labels", "", "Comma-separated list of additional label pairs to add (e.g. 'Cipher=AES-128-CBC,Operation=ENCRYPT')") swapIVAndPlaintext = flag.Bool("swap-iv-plaintext", false, "When processing CBC vector files for CTR mode, swap IV and plaintext.") ) type kvPair struct { key, value string } // Test generates a FileTest file from a CAVP response file. type Test struct { translations map[kvPair]kvPair transform func(k, v string) []kvPair defaults map[string]string // kvDelim is the rune that delimits key-value pairs throughout the file ('=' or ':'). kvDelim rune } func (t *Test) parseKeyValue(s string) (key, value string) { if t.kvDelim == 0 { i := strings.IndexAny(s, "=:") if i != -1 { t.kvDelim = rune(s[i]) } } if i := strings.IndexRune(s, t.kvDelim); t.kvDelim != 0 && i != -1 { key, value = s[:i], s[i+1:] if trimmed := strings.TrimSpace(value); len(trimmed) != 0 { value = trimmed } else { value = " " } } else { key = s } return strings.TrimSpace(key), value } func (t *Test) translateKeyValue(key, value string) (string, string) { if kv, ok := t.translations[kvPair{key, ""}]; ok { if len(kv.value) == 0 && len(value) != 0 { return kv.key, value } return kv.key, kv.value } if kv, ok := t.translations[kvPair{key, value}]; ok { return kv.key, kv.value } return key, value } func printKeyValue(key, value string) { if len(value) == 0 { fmt.Println(key) } else { // Omit the value if it is " ", i.e. print "key: ", not "key: ". value = strings.TrimSpace(value) fmt.Printf("%s: %s\n", key, value) } } func (t *Test) generate(r io.Reader, cmdLineLabelStr string) { s := bufio.NewScanner(r) // Label blocks consist of lines of the form "[key]" or "[key = // value]". |labels| holds keys and values of the most recent block // of labels. var labels map[string]string // Auxiliary labels passed as a flag. cmdLineLabels := make(map[string]string) if len(cmdLineLabelStr) != 0 { pairs := strings.Split(cmdLineLabelStr, ",") for _, p := range pairs { key, value := t.parseKeyValue(p) cmdLineLabels[key] = value } } t.kvDelim = 0 // Reset kvDelim for scanning the file. // Whether we are in a test or a label section. inLabels := false inTest := false n := 0 var currentKv map[string]string for s.Scan() { n++ line := s.Text() l := strings.TrimSpace(line) l = strings.SplitN(l, "#", 2)[0] // Trim trailing comments. // Blank line. if len(l) == 0 { if inTest { // Fill in missing defaults at the end of a test case. for k, v := range t.defaults { if _, ok := currentKv[k]; !ok { printKeyValue(k, v) } } fmt.Println() } inTest = false currentKv = make(map[string]string) inLabels = false continue } // Label section. if l[0] == '[' { if l[len(l)-1] != ']' { log.Fatalf("line #%d invalid: %q", n, line) } if !inLabels { labels = make(map[string]string) inLabels = true } k, v := t.parseKeyValue(l[1 : len(l)-1]) k, v = t.translateKeyValue(k, v) if len(k) != 0 { labels[k] = v } continue } // Repeat the label map at the beginning of each test section. if !inTest { inTest = true for k, v := range cmdLineLabels { printKeyValue(k, v) currentKv[k] = v } for k, v := range labels { printKeyValue(k, v) currentKv[k] = v } } // Look up translation and apply transformation (if any). k, v := t.parseKeyValue(l) k, v = t.translateKeyValue(k, v) kvPairs := []kvPair{{k, v}} if t.transform != nil { kvPairs = t.transform(k, v) } for _, kv := range kvPairs { k, v := kv.key, kv.value if *cipher == "tdes" && k == "Key" { v += v + v // Key1=Key2=Key3 } if len(k) != 0 { printKeyValue(k, v) currentKv[k] = v } } } } func usage() { fmt.Fprintln(os.Stderr, "usage: make_cavp [ ...]") flag.PrintDefaults() } func maybeSwapIVAndPlaintext(k, v string) []kvPair { if *swapIVAndPlaintext { if k == "Plaintext" { return []kvPair{{"IV", v}} } else if k == "IV" { return []kvPair{{"Plaintext", v}} } } return []kvPair{{k, v}} } // Test generator for different values of the -cipher flag. var testMap = map[string]Test{ // Generate cipher_test input file from AESVS .rsp file. "aes": Test{ translations: map[kvPair]kvPair{ {"ENCRYPT", ""}: {"Operation", "ENCRYPT"}, {"DECRYPT", ""}: {"Operation", "DECRYPT"}, {"COUNT", ""}: {"Count", ""}, {"KEY", ""}: {"Key", ""}, {"PLAINTEXT", ""}: {"Plaintext", ""}, {"CIPHERTEXT", ""}: {"Ciphertext", ""}, {"COUNT", ""}: {"", ""}, // delete }, transform: maybeSwapIVAndPlaintext, }, // Generate cipher_test input file from TMOVS .rsp file. "tdes": Test{ translations: map[kvPair]kvPair{ {"ENCRYPT", ""}: {"Operation", "ENCRYPT"}, {"DECRYPT", ""}: {"Operation", "DECRYPT"}, {"COUNT", ""}: {"Count", ""}, {"KEYs", ""}: {"Key", ""}, {"PLAINTEXT", ""}: {"Plaintext", ""}, {"CIPHERTEXT", ""}: {"Ciphertext", ""}, {"COUNT", ""}: {"", ""}, // delete }, transform: maybeSwapIVAndPlaintext, }, // Generate aead_test input file from GCMVS .rsp file. "gcm": Test{ translations: map[kvPair]kvPair{ {"Keylen", ""}: {"", ""}, // delete {"IVlen", ""}: {"", ""}, // delete {"PTlen", ""}: {"", ""}, // delete {"AADlen", ""}: {"", ""}, // delete {"Taglen", ""}: {"", ""}, // delete {"Count", ""}: {"", ""}, // delete {"Key", ""}: {"KEY", ""}, {"IV", ""}: {"NONCE", ""}, {"PT", ""}: {"IN", ""}, {"AAD", ""}: {"AD", ""}, {"Tag", ""}: {"TAG", ""}, {"FAIL", ""}: {"FAILS", " "}, }, transform: func(k, v string) []kvPair { if k == "FAILS" { // FAIL cases only appear in the decrypt rsp files. Skip encryption for // these. return []kvPair{{"FAILS", " "}, {"NO_SEAL", " "}} } return []kvPair{{k, v}} }, defaults: map[string]string{ "IN": " ", // FAIL tests don't have IN }, }, } func main() { flag.Usage = usage flag.Parse() if len(flag.Args()) == 0 { fmt.Fprintf(os.Stderr, "no input files\n\n") flag.Usage() os.Exit(1) } test, ok := testMap[*cipher] if !ok { fmt.Fprintf(os.Stderr, "invalid cipher: %q\n\n", *cipher) flag.Usage() os.Exit(1) } args := append([]string{"make_cavp"}, os.Args[1:]...) fmt.Printf("# Generated by %q\n\n", strings.Join(args, " ")) for i, p := range flag.Args() { f, err := os.Open(p) if err != nil { log.Fatal(err) } defer f.Close() fmt.Printf("# File %d: %s\n\n", i+1, p) test.generate(f, *cmdLineLabelStr) } }