1 /* Copyright (c) 2015, Google Inc.
2 *
3 * Permission to use, copy, modify, and/or distribute this software for any
4 * purpose with or without fee is hereby granted, provided that the above
5 * copyright notice and this permission notice appear in all copies.
6 *
7 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
8 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
9 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
10 * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
11 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
12 * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
13 * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */
14
15 #include "file_test.h"
16
17 #include <algorithm>
18 #include <utility>
19
20 #include <assert.h>
21 #include <ctype.h>
22 #include <errno.h>
23 #include <limits.h>
24 #include <stdarg.h>
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h>
28
29 #include <openssl/err.h>
30 #include <openssl/mem.h>
31
32 #include "../internal.h"
33 #include "./test_util.h"
34
35
FileTest(std::unique_ptr<FileTest::LineReader> reader,std::function<void (const std::string &)> comment_callback,bool is_kas_test)36 FileTest::FileTest(std::unique_ptr<FileTest::LineReader> reader,
37 std::function<void(const std::string &)> comment_callback,
38 bool is_kas_test)
39 : reader_(std::move(reader)),
40 is_kas_test_(is_kas_test),
41 comment_callback_(std::move(comment_callback)) {}
42
~FileTest()43 FileTest::~FileTest() {}
44
45 // FindDelimiter returns a pointer to the first '=' or ':' in |str| or nullptr
46 // if there is none.
FindDelimiter(const char * str)47 static const char *FindDelimiter(const char *str) {
48 while (*str) {
49 if (*str == ':' || *str == '=') {
50 return str;
51 }
52 str++;
53 }
54 return nullptr;
55 }
56
57 // StripSpace returns a string containing up to |len| characters from |str| with
58 // leading and trailing whitespace removed.
StripSpace(const char * str,size_t len)59 static std::string StripSpace(const char *str, size_t len) {
60 // Remove leading space.
61 while (len > 0 && OPENSSL_isspace(*str)) {
62 str++;
63 len--;
64 }
65 while (len > 0 && OPENSSL_isspace(str[len - 1])) {
66 len--;
67 }
68 return std::string(str, len);
69 }
70
ParseKeyValue(const char * str,const size_t len)71 static std::pair<std::string, std::string> ParseKeyValue(const char *str, const size_t len) {
72 const char *delimiter = FindDelimiter(str);
73 std::string key, value;
74 if (delimiter == nullptr) {
75 key = StripSpace(str, len);
76 } else {
77 key = StripSpace(str, delimiter - str);
78 value = StripSpace(delimiter + 1, str + len - delimiter - 1);
79 }
80 return {key, value};
81 }
82
ReadNext()83 FileTest::ReadResult FileTest::ReadNext() {
84 // If the previous test had unused attributes or instructions, it is an error.
85 if (!unused_attributes_.empty()) {
86 for (const std::string &key : unused_attributes_) {
87 PrintLine("Unused attribute: %s", key.c_str());
88 }
89 return kReadError;
90 }
91 if (!unused_instructions_.empty()) {
92 for (const std::string &key : unused_instructions_) {
93 PrintLine("Unused instruction: %s", key.c_str());
94 }
95 return kReadError;
96 }
97
98 ClearTest();
99
100 static const size_t kBufLen = 8192 * 4;
101 auto buf = std::make_unique<char[]>(kBufLen);
102
103 bool in_instruction_block = false;
104 is_at_new_instruction_block_ = false;
105
106 while (true) {
107 // Read the next line.
108 switch (reader_->ReadLine(buf.get(), kBufLen)) {
109 case kReadError:
110 fprintf(stderr, "Error reading from input at line %u.\n", line_ + 1);
111 return kReadError;
112 case kReadEOF:
113 // EOF is a valid terminator for a test.
114 return start_line_ > 0 ? kReadSuccess : kReadEOF;
115 case kReadSuccess:
116 break;
117 }
118
119 line_++;
120 size_t len = strlen(buf.get());
121 if (buf[0] == '\n' || buf[0] == '\r' || buf[0] == '\0') {
122 // Empty lines delimit tests.
123 if (start_line_ > 0) {
124 return kReadSuccess;
125 }
126 if (in_instruction_block) {
127 in_instruction_block = false;
128 // Delimit instruction block from test with a blank line.
129 current_test_ += "\r\n";
130 } else if (is_kas_test_) {
131 // KAS tests have random blank lines scattered around.
132 current_test_ += "\r\n";
133 }
134 } else if (buf[0] == '#') {
135 if (is_kas_test_ && seen_non_comment_) {
136 // KAS tests have comments after the initial comment block which need
137 // to be included in the corresponding place in the output.
138 current_test_ += std::string(buf.get());
139 } else if (comment_callback_) {
140 comment_callback_(buf.get());
141 }
142 // Otherwise ignore comments.
143 } else if (strcmp("[B.4.2 Key Pair Generation by Testing Candidates]\r\n",
144 buf.get()) == 0) {
145 // The above instruction-like line is ignored because the FIPS lab's
146 // request files are hopelessly inconsistent.
147 } else if (buf[0] == '[') { // Inside an instruction block.
148 is_at_new_instruction_block_ = true;
149 seen_non_comment_ = true;
150 if (start_line_ != 0) {
151 // Instructions should be separate blocks.
152 fprintf(stderr, "Line %u is an instruction in a test case.\n", line_);
153 return kReadError;
154 }
155 if (!in_instruction_block) {
156 ClearInstructions();
157 in_instruction_block = true;
158 }
159
160 // Parse the line as an instruction ("[key = value]" or "[key]").
161
162 // KAS tests contain invalid syntax.
163 std::string kv = buf.get();
164 const bool is_broken_kas_instruction =
165 is_kas_test_ &&
166 (kv == "[SHA(s) supported (Used for hashing Z): SHA512 \r\n");
167
168 if (!is_broken_kas_instruction) {
169 kv = StripSpace(buf.get(), len);
170 if (kv[kv.size() - 1] != ']') {
171 fprintf(stderr, "Line %u, invalid instruction: '%s'\n", line_,
172 kv.c_str());
173 return kReadError;
174 }
175 } else {
176 // Just remove the newline for the broken instruction.
177 kv = kv.substr(0, kv.size() - 2);
178 }
179
180 current_test_ += kv + "\r\n";
181 kv = std::string(kv.begin() + 1, kv.end() - 1);
182
183 for (;;) {
184 size_t idx = kv.find(',');
185 if (idx == std::string::npos) {
186 idx = kv.size();
187 }
188 std::string key, value;
189 std::tie(key, value) = ParseKeyValue(kv.c_str(), idx);
190 instructions_[key] = value;
191 if (idx == kv.size())
192 break;
193 kv = kv.substr(idx + 1);
194 }
195 } else {
196 // Parsing a test case.
197 if (in_instruction_block) {
198 // Some NIST CAVP test files (TDES) have a test case immediately
199 // following an instruction block, without a separate blank line, some
200 // of the time.
201 in_instruction_block = false;
202 }
203
204 current_test_ += std::string(buf.get(), len);
205 std::string key, value;
206 std::tie(key, value) = ParseKeyValue(buf.get(), len);
207
208 // Duplicate keys are rewritten to have “/2”, “/3”, … suffixes.
209 std::string mapped_key = key;
210 // If absent, the value will be zero-initialized.
211 const size_t num_occurrences = ++attribute_count_[key];
212 if (num_occurrences > 1) {
213 mapped_key += "/" + std::to_string(num_occurrences);
214 }
215
216 unused_attributes_.insert(mapped_key);
217 attributes_[mapped_key] = value;
218 if (start_line_ == 0) {
219 // This is the start of a test.
220 type_ = mapped_key;
221 parameter_ = value;
222 start_line_ = line_;
223 for (const auto &kv : instructions_) {
224 unused_instructions_.insert(kv.first);
225 }
226 }
227 }
228 }
229 }
230
PrintLine(const char * format,...)231 void FileTest::PrintLine(const char *format, ...) {
232 va_list args;
233 va_start(args, format);
234
235 fprintf(stderr, "Line %u: ", start_line_);
236 vfprintf(stderr, format, args);
237 fprintf(stderr, "\n");
238
239 va_end(args);
240 }
241
GetType()242 const std::string &FileTest::GetType() {
243 OnKeyUsed(type_);
244 return type_;
245 }
246
GetParameter()247 const std::string &FileTest::GetParameter() {
248 OnKeyUsed(type_);
249 return parameter_;
250 }
251
HasAttribute(const std::string & key)252 bool FileTest::HasAttribute(const std::string &key) {
253 OnKeyUsed(key);
254 return attributes_.count(key) > 0;
255 }
256
GetAttribute(std::string * out_value,const std::string & key)257 bool FileTest::GetAttribute(std::string *out_value, const std::string &key) {
258 OnKeyUsed(key);
259 auto iter = attributes_.find(key);
260 if (iter == attributes_.end()) {
261 PrintLine("Missing attribute '%s'.", key.c_str());
262 return false;
263 }
264 *out_value = iter->second;
265 return true;
266 }
267
GetAttributeOrDie(const std::string & key)268 const std::string &FileTest::GetAttributeOrDie(const std::string &key) {
269 if (!HasAttribute(key)) {
270 abort();
271 }
272 return attributes_[key];
273 }
274
HasInstruction(const std::string & key)275 bool FileTest::HasInstruction(const std::string &key) {
276 OnInstructionUsed(key);
277 return instructions_.count(key) > 0;
278 }
279
GetInstruction(std::string * out_value,const std::string & key)280 bool FileTest::GetInstruction(std::string *out_value, const std::string &key) {
281 OnInstructionUsed(key);
282 auto iter = instructions_.find(key);
283 if (iter == instructions_.end()) {
284 PrintLine("Missing instruction '%s'.", key.c_str());
285 return false;
286 }
287 *out_value = iter->second;
288 return true;
289 }
290
IgnoreAllUnusedInstructions()291 void FileTest::IgnoreAllUnusedInstructions() {
292 unused_instructions_.clear();
293 }
294
GetInstructionOrDie(const std::string & key)295 const std::string &FileTest::GetInstructionOrDie(const std::string &key) {
296 if (!HasInstruction(key)) {
297 abort();
298 }
299 return instructions_[key];
300 }
301
GetInstructionBytes(std::vector<uint8_t> * out,const std::string & key)302 bool FileTest::GetInstructionBytes(std::vector<uint8_t> *out,
303 const std::string &key) {
304 std::string value;
305 return GetInstruction(&value, key) && ConvertToBytes(out, value);
306 }
307
CurrentTestToString() const308 const std::string &FileTest::CurrentTestToString() const {
309 return current_test_;
310 }
311
GetBytes(std::vector<uint8_t> * out,const std::string & key)312 bool FileTest::GetBytes(std::vector<uint8_t> *out, const std::string &key) {
313 std::string value;
314 return GetAttribute(&value, key) && ConvertToBytes(out, value);
315 }
316
ClearTest()317 void FileTest::ClearTest() {
318 start_line_ = 0;
319 type_.clear();
320 parameter_.clear();
321 attribute_count_.clear();
322 attributes_.clear();
323 unused_attributes_.clear();
324 unused_instructions_.clear();
325 current_test_ = "";
326 }
327
ClearInstructions()328 void FileTest::ClearInstructions() {
329 instructions_.clear();
330 unused_attributes_.clear();
331 }
332
OnKeyUsed(const std::string & key)333 void FileTest::OnKeyUsed(const std::string &key) {
334 unused_attributes_.erase(key);
335 }
336
OnInstructionUsed(const std::string & key)337 void FileTest::OnInstructionUsed(const std::string &key) {
338 unused_instructions_.erase(key);
339 }
340
ConvertToBytes(std::vector<uint8_t> * out,const std::string & value)341 bool FileTest::ConvertToBytes(std::vector<uint8_t> *out,
342 const std::string &value) {
343 if (value.size() >= 2 && value[0] == '"' && value[value.size() - 1] == '"') {
344 out->assign(value.begin() + 1, value.end() - 1);
345 return true;
346 }
347
348 if (!DecodeHex(out, value)) {
349 PrintLine("Error decoding value: %s", value.c_str());
350 return false;
351 }
352 return true;
353 }
354
IsAtNewInstructionBlock() const355 bool FileTest::IsAtNewInstructionBlock() const {
356 return is_at_new_instruction_block_;
357 }
358
InjectInstruction(const std::string & key,const std::string & value)359 void FileTest::InjectInstruction(const std::string &key,
360 const std::string &value) {
361 instructions_[key] = value;
362 }
363
364 class FileLineReader : public FileTest::LineReader {
365 public:
FileLineReader(const char * path)366 explicit FileLineReader(const char *path) : file_(fopen(path, "r")) {}
~FileLineReader()367 ~FileLineReader() override {
368 if (file_ != nullptr) {
369 fclose(file_);
370 }
371 }
372
373 // is_open returns true if the file was successfully opened.
is_open() const374 bool is_open() const { return file_ != nullptr; }
375
ReadLine(char * out,size_t len)376 FileTest::ReadResult ReadLine(char *out, size_t len) override {
377 assert(len > 0);
378 if (file_ == nullptr) {
379 return FileTest::kReadError;
380 }
381
382 len = std::min(len, size_t{INT_MAX});
383 if (fgets(out, static_cast<int>(len), file_) == nullptr) {
384 return feof(file_) ? FileTest::kReadEOF : FileTest::kReadError;
385 }
386
387 if (strlen(out) == len - 1 && out[len - 2] != '\n' && !feof(file_)) {
388 fprintf(stderr, "Line too long.\n");
389 return FileTest::kReadError;
390 }
391
392 return FileTest::kReadSuccess;
393 }
394
395 private:
396 FILE *file_;
397
398 FileLineReader(const FileLineReader &) = delete;
399 FileLineReader &operator=(const FileLineReader &) = delete;
400 };
401
FileTestMain(FileTestFunc run_test,void * arg,const char * path)402 int FileTestMain(FileTestFunc run_test, void *arg, const char *path) {
403 FileTest::Options opts;
404 opts.callback = run_test;
405 opts.arg = arg;
406 opts.path = path;
407
408 return FileTestMain(opts);
409 }
410
FileTestMain(const FileTest::Options & opts)411 int FileTestMain(const FileTest::Options &opts) {
412 auto reader = std::make_unique<FileLineReader>(opts.path);
413 if (!reader->is_open()) {
414 fprintf(stderr, "Could not open file %s: %s.\n", opts.path,
415 strerror(errno));
416 return 1;
417 }
418
419 FileTest t(std::move(reader), opts.comment_callback, opts.is_kas_test);
420
421 bool failed = false;
422 while (true) {
423 FileTest::ReadResult ret = t.ReadNext();
424 if (ret == FileTest::kReadError) {
425 return 1;
426 } else if (ret == FileTest::kReadEOF) {
427 break;
428 }
429
430 bool result = opts.callback(&t, opts.arg);
431 if (t.HasAttribute("Error")) {
432 if (result) {
433 t.PrintLine("Operation unexpectedly succeeded.");
434 failed = true;
435 continue;
436 }
437 uint32_t err = ERR_peek_error();
438 if (ERR_reason_error_string(err) != t.GetAttributeOrDie("Error")) {
439 t.PrintLine("Unexpected error; wanted '%s', got '%s'.",
440 t.GetAttributeOrDie("Error").c_str(),
441 ERR_reason_error_string(err));
442 failed = true;
443 ERR_clear_error();
444 continue;
445 }
446 ERR_clear_error();
447 } else if (!result) {
448 // In case the test itself doesn't print output, print something so the
449 // line number is reported.
450 t.PrintLine("Test failed");
451 ERR_print_errors_fp(stderr);
452 failed = true;
453 continue;
454 }
455 }
456
457 if (!opts.silent && !failed) {
458 printf("PASS\n");
459 }
460
461 return failed ? 1 : 0;
462 }
463
SkipCurrent()464 void FileTest::SkipCurrent() {
465 ClearTest();
466 }
467