xref: /aosp_15_r20/external/boringssl/src/crypto/test/file_test.cc (revision 8fb009dc861624b67b6cdb62ea21f0f22d0c584b)
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