1// Copyright 2017 The BoringSSL Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15// The make_cavp utility generates cipher_test input files from NIST CAVP Known
16// Answer Test response (.rsp) files.
17package main
18
19import (
20	"bufio"
21	"flag"
22	"fmt"
23	"io"
24	"log"
25	"os"
26	"strings"
27)
28
29var (
30	cipher             = flag.String("cipher", "", "The name of the cipher/mode (supported: aes, tdes, gcm). Required.")
31	cmdLineLabelStr    = flag.String("extra-labels", "", "Comma-separated list of additional label pairs to add (e.g. 'Cipher=AES-128-CBC,Operation=ENCRYPT')")
32	swapIVAndPlaintext = flag.Bool("swap-iv-plaintext", false, "When processing CBC vector files for CTR mode, swap IV and plaintext.")
33)
34
35type kvPair struct {
36	key, value string
37}
38
39// Test generates a FileTest file from a CAVP response file.
40type Test struct {
41	translations map[kvPair]kvPair
42	transform    func(k, v string) []kvPair
43	defaults     map[string]string
44	// kvDelim is the rune that delimits key-value pairs throughout the file ('=' or ':').
45	kvDelim rune
46}
47
48func (t *Test) parseKeyValue(s string) (key, value string) {
49	if t.kvDelim == 0 {
50		i := strings.IndexAny(s, "=:")
51		if i != -1 {
52			t.kvDelim = rune(s[i])
53		}
54	}
55	if i := strings.IndexRune(s, t.kvDelim); t.kvDelim != 0 && i != -1 {
56		key, value = s[:i], s[i+1:]
57		if trimmed := strings.TrimSpace(value); len(trimmed) != 0 {
58			value = trimmed
59		} else {
60			value = " "
61		}
62	} else {
63		key = s
64	}
65	return strings.TrimSpace(key), value
66}
67
68func (t *Test) translateKeyValue(key, value string) (string, string) {
69	if kv, ok := t.translations[kvPair{key, ""}]; ok {
70		if len(kv.value) == 0 && len(value) != 0 {
71			return kv.key, value
72		}
73		return kv.key, kv.value
74	}
75	if kv, ok := t.translations[kvPair{key, value}]; ok {
76		return kv.key, kv.value
77	}
78	return key, value
79}
80
81func printKeyValue(key, value string) {
82	if len(value) == 0 {
83		fmt.Println(key)
84	} else {
85		// Omit the value if it is " ", i.e. print "key: ", not "key:  ".
86		value = strings.TrimSpace(value)
87		fmt.Printf("%s: %s\n", key, value)
88	}
89}
90
91func (t *Test) generate(r io.Reader, cmdLineLabelStr string) {
92	s := bufio.NewScanner(r)
93
94	// Label blocks consist of lines of the form "[key]" or "[key =
95	// value]". |labels| holds keys and values of the most recent block
96	// of labels.
97	var labels map[string]string
98
99	// Auxiliary labels passed as a flag.
100	cmdLineLabels := make(map[string]string)
101	if len(cmdLineLabelStr) != 0 {
102		pairs := strings.Split(cmdLineLabelStr, ",")
103		for _, p := range pairs {
104			key, value := t.parseKeyValue(p)
105			cmdLineLabels[key] = value
106		}
107	}
108
109	t.kvDelim = 0 // Reset kvDelim for scanning the file.
110
111	// Whether we are in a test or a label section.
112	inLabels := false
113	inTest := false
114
115	n := 0
116	var currentKv map[string]string
117	for s.Scan() {
118		n++
119		line := s.Text()
120		l := strings.TrimSpace(line)
121		l = strings.SplitN(l, "#", 2)[0] // Trim trailing comments.
122
123		// Blank line.
124		if len(l) == 0 {
125			if inTest {
126				// Fill in missing defaults at the end of a test case.
127				for k, v := range t.defaults {
128					if _, ok := currentKv[k]; !ok {
129						printKeyValue(k, v)
130					}
131				}
132				fmt.Println()
133			}
134			inTest = false
135			currentKv = make(map[string]string)
136			inLabels = false
137			continue
138		}
139
140		// Label section.
141		if l[0] == '[' {
142			if l[len(l)-1] != ']' {
143				log.Fatalf("line #%d invalid: %q", n, line)
144			}
145			if !inLabels {
146				labels = make(map[string]string)
147				inLabels = true
148			}
149
150			k, v := t.parseKeyValue(l[1 : len(l)-1])
151			k, v = t.translateKeyValue(k, v)
152			if len(k) != 0 {
153				labels[k] = v
154			}
155
156			continue
157		}
158
159		// Repeat the label map at the beginning of each test section.
160		if !inTest {
161			inTest = true
162			for k, v := range cmdLineLabels {
163				printKeyValue(k, v)
164				currentKv[k] = v
165			}
166			for k, v := range labels {
167				printKeyValue(k, v)
168				currentKv[k] = v
169			}
170		}
171
172		// Look up translation and apply transformation (if any).
173		k, v := t.parseKeyValue(l)
174		k, v = t.translateKeyValue(k, v)
175		kvPairs := []kvPair{{k, v}}
176		if t.transform != nil {
177			kvPairs = t.transform(k, v)
178		}
179
180		for _, kv := range kvPairs {
181			k, v := kv.key, kv.value
182			if *cipher == "tdes" && k == "Key" {
183				v += v + v // Key1=Key2=Key3
184			}
185			if len(k) != 0 {
186				printKeyValue(k, v)
187				currentKv[k] = v
188			}
189		}
190	}
191}
192
193func usage() {
194	fmt.Fprintln(os.Stderr, "usage: make_cavp <file 1> [<file 2> ...]")
195	flag.PrintDefaults()
196}
197
198func maybeSwapIVAndPlaintext(k, v string) []kvPair {
199	if *swapIVAndPlaintext {
200		if k == "Plaintext" {
201			return []kvPair{{"IV", v}}
202		} else if k == "IV" {
203			return []kvPair{{"Plaintext", v}}
204		}
205	}
206	return []kvPair{{k, v}}
207}
208
209// Test generator for different values of the -cipher flag.
210var testMap = map[string]Test{
211	// Generate cipher_test input file from AESVS .rsp file.
212	"aes": Test{
213		translations: map[kvPair]kvPair{
214			{"ENCRYPT", ""}:    {"Operation", "ENCRYPT"},
215			{"DECRYPT", ""}:    {"Operation", "DECRYPT"},
216			{"COUNT", ""}:      {"Count", ""},
217			{"KEY", ""}:        {"Key", ""},
218			{"PLAINTEXT", ""}:  {"Plaintext", ""},
219			{"CIPHERTEXT", ""}: {"Ciphertext", ""},
220			{"COUNT", ""}:      {"", ""}, // delete
221		},
222		transform: maybeSwapIVAndPlaintext,
223	},
224	// Generate cipher_test input file from TMOVS .rsp file.
225	"tdes": Test{
226		translations: map[kvPair]kvPair{
227			{"ENCRYPT", ""}:    {"Operation", "ENCRYPT"},
228			{"DECRYPT", ""}:    {"Operation", "DECRYPT"},
229			{"COUNT", ""}:      {"Count", ""},
230			{"KEYs", ""}:       {"Key", ""},
231			{"PLAINTEXT", ""}:  {"Plaintext", ""},
232			{"CIPHERTEXT", ""}: {"Ciphertext", ""},
233			{"COUNT", ""}:      {"", ""}, // delete
234		},
235		transform: maybeSwapIVAndPlaintext,
236	},
237	// Generate aead_test input file from GCMVS .rsp file.
238	"gcm": Test{
239		translations: map[kvPair]kvPair{
240			{"Keylen", ""}: {"", ""}, // delete
241			{"IVlen", ""}:  {"", ""}, // delete
242			{"PTlen", ""}:  {"", ""}, // delete
243			{"AADlen", ""}: {"", ""}, // delete
244			{"Taglen", ""}: {"", ""}, // delete
245			{"Count", ""}:  {"", ""}, // delete
246			{"Key", ""}:    {"KEY", ""},
247			{"IV", ""}:     {"NONCE", ""},
248			{"PT", ""}:     {"IN", ""},
249			{"AAD", ""}:    {"AD", ""},
250			{"Tag", ""}:    {"TAG", ""},
251			{"FAIL", ""}:   {"FAILS", " "},
252		},
253		transform: func(k, v string) []kvPair {
254			if k == "FAILS" {
255				// FAIL cases only appear in the decrypt rsp files. Skip encryption for
256				// these.
257				return []kvPair{{"FAILS", " "}, {"NO_SEAL", " "}}
258			}
259			return []kvPair{{k, v}}
260		},
261		defaults: map[string]string{
262			"IN": " ", // FAIL tests don't have IN
263		},
264	},
265}
266
267func main() {
268	flag.Usage = usage
269	flag.Parse()
270
271	if len(flag.Args()) == 0 {
272		fmt.Fprintf(os.Stderr, "no input files\n\n")
273		flag.Usage()
274		os.Exit(1)
275	}
276
277	test, ok := testMap[*cipher]
278	if !ok {
279		fmt.Fprintf(os.Stderr, "invalid cipher: %q\n\n", *cipher)
280		flag.Usage()
281		os.Exit(1)
282	}
283
284	args := append([]string{"make_cavp"}, os.Args[1:]...)
285	fmt.Printf("# Generated by %q\n\n", strings.Join(args, " "))
286
287	for i, p := range flag.Args() {
288		f, err := os.Open(p)
289		if err != nil {
290			log.Fatal(err)
291		}
292		defer f.Close()
293
294		fmt.Printf("# File %d: %s\n\n", i+1, p)
295		test.generate(f, *cmdLineLabelStr)
296	}
297}
298