xref: /aosp_15_r20/external/cronet/third_party/boringssl/src/tool/digest.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
1 /* Copyright (c) 2014, 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 <openssl/base.h>
16 
17 #include <memory>
18 #include <string>
19 #include <vector>
20 
21 #include <errno.h>
22 #include <fcntl.h>
23 #include <limits.h>
24 #include <stdio.h>
25 #include <sys/stat.h>
26 #include <sys/types.h>
27 
28 #if !defined(OPENSSL_WINDOWS)
29 #include <string.h>
30 #include <unistd.h>
31 #if !defined(O_BINARY)
32 #define O_BINARY 0
33 #endif
34 #else
35 OPENSSL_MSVC_PRAGMA(warning(push, 3))
36 #include <windows.h>
37 OPENSSL_MSVC_PRAGMA(warning(pop))
38 #include <io.h>
39 #if !defined(PATH_MAX)
40 #define PATH_MAX MAX_PATH
41 #endif
42 #endif
43 
44 #include <openssl/digest.h>
45 
46 #include "internal.h"
47 
48 
49 // Source is an awkward expression of a union type in C++: Stdin | File filename.
50 struct Source {
51   enum Type {
52     STDIN,
53   };
54 
SourceSource55   Source() : is_stdin_(false) {}
SourceSource56   explicit Source(Type) : is_stdin_(true) {}
SourceSource57   explicit Source(const std::string &name)
58       : is_stdin_(false), filename_(name) {}
59 
is_stdinSource60   bool is_stdin() const { return is_stdin_; }
filenameSource61   const std::string &filename() const { return filename_; }
62 
63  private:
64   bool is_stdin_;
65   std::string filename_;
66 };
67 
68 static const char kStdinName[] = "standard input";
69 
70 // OpenFile opens the regular file named |filename| and returns a file
71 // descriptor to it.
OpenFile(const std::string & filename)72 static ScopedFD OpenFile(const std::string &filename) {
73   ScopedFD fd = OpenFD(filename.c_str(), O_RDONLY | O_BINARY);
74   if (!fd) {
75     fprintf(stderr, "Failed to open input file '%s': %s\n", filename.c_str(),
76             strerror(errno));
77     return ScopedFD();
78   }
79 
80 #if !defined(OPENSSL_WINDOWS)
81   struct stat st;
82   if (fstat(fd.get(), &st)) {
83     fprintf(stderr, "Failed to stat input file '%s': %s\n", filename.c_str(),
84             strerror(errno));
85     return ScopedFD();
86   }
87 
88   if (!S_ISREG(st.st_mode)) {
89     fprintf(stderr, "%s: not a regular file\n", filename.c_str());
90     return ScopedFD();
91   }
92 #endif
93 
94   return fd;
95 }
96 
97 // SumFile hashes the contents of |source| with |md| and sets |*out_hex| to the
98 // hex-encoded result.
99 //
100 // It returns true on success or prints an error to stderr and returns false on
101 // error.
SumFile(std::string * out_hex,const EVP_MD * md,const Source & source)102 static bool SumFile(std::string *out_hex, const EVP_MD *md,
103                     const Source &source) {
104   ScopedFD scoped_fd;
105   int fd;
106 
107   if (source.is_stdin()) {
108     fd = 0;
109   } else {
110     scoped_fd = OpenFile(source.filename());
111     if (!scoped_fd) {
112       return false;
113     }
114     fd = scoped_fd.get();
115   }
116 
117   static const size_t kBufSize = 8192;
118   auto buf = std::make_unique<uint8_t[]>(kBufSize);
119 
120   bssl::ScopedEVP_MD_CTX ctx;
121   if (!EVP_DigestInit_ex(ctx.get(), md, NULL)) {
122     fprintf(stderr, "Failed to initialize EVP_MD_CTX.\n");
123     return false;
124   }
125 
126   for (;;) {
127     size_t n;
128     if (!ReadFromFD(fd, &n, buf.get(), kBufSize)) {
129       fprintf(stderr, "Failed to read from %s: %s\n",
130               source.is_stdin() ? kStdinName : source.filename().c_str(),
131               strerror(errno));
132       return false;
133     }
134 
135     if (n == 0) {
136       break;
137     }
138 
139     if (!EVP_DigestUpdate(ctx.get(), buf.get(), n)) {
140       fprintf(stderr, "Failed to update hash.\n");
141       return false;
142     }
143   }
144 
145   uint8_t digest[EVP_MAX_MD_SIZE];
146   unsigned digest_len;
147   if (!EVP_DigestFinal_ex(ctx.get(), digest, &digest_len)) {
148     fprintf(stderr, "Failed to finish hash.\n");
149     return false;
150   }
151 
152   char hex_digest[EVP_MAX_MD_SIZE * 2];
153   static const char kHextable[] = "0123456789abcdef";
154   for (unsigned i = 0; i < digest_len; i++) {
155     const uint8_t b = digest[i];
156     hex_digest[i * 2] = kHextable[b >> 4];
157     hex_digest[i * 2 + 1] = kHextable[b & 0xf];
158   }
159   *out_hex = std::string(hex_digest, digest_len * 2);
160 
161   return true;
162 }
163 
164 // PrintFileSum hashes |source| with |md| and prints a line to stdout in the
165 // format of the coreutils *sum utilities. It returns true on success or prints
166 // an error to stderr and returns false on error.
PrintFileSum(const EVP_MD * md,const Source & source)167 static bool PrintFileSum(const EVP_MD *md, const Source &source) {
168   std::string hex_digest;
169   if (!SumFile(&hex_digest, md, source)) {
170     return false;
171   }
172 
173   // TODO: When given "--binary" or "-b", we should print " *" instead of "  "
174   // between the digest and the filename.
175   //
176   // MSYS and Cygwin md5sum default to binary mode by default, whereas other
177   // platforms' tools default to text mode by default. We default to text mode
178   // by default and consider text mode equivalent to binary mode (i.e. we
179   // always use Unix semantics, even on Windows), which means that our default
180   // output will differ from the MSYS and Cygwin tools' default output.
181   printf("%s  %s\n", hex_digest.c_str(),
182          source.is_stdin() ? "-" : source.filename().c_str());
183   return true;
184 }
185 
186 // CheckModeArguments contains arguments for the check mode. See the
187 // sha256sum(1) man page for details.
188 struct CheckModeArguments {
189   bool quiet = false;
190   bool status = false;
191   bool warn = false;
192   bool strict = false;
193 };
194 
195 // Check reads lines from |source| where each line is in the format of the
196 // coreutils *sum utilities. It attempts to verify each hash by reading the
197 // file named in the line.
198 //
199 // It returns true if all files were verified and, if |args.strict|, no input
200 // lines had formatting errors. Otherwise it prints errors to stderr and
201 // returns false.
Check(const CheckModeArguments & args,const EVP_MD * md,const Source & source)202 static bool Check(const CheckModeArguments &args, const EVP_MD *md,
203                   const Source &source) {
204   FILE *file;
205   ScopedFILE scoped_file;
206 
207   if (source.is_stdin()) {
208     file = stdin;
209   } else {
210     ScopedFD fd = OpenFile(source.filename());
211     if (!fd) {
212       return false;
213     }
214 
215     scoped_file = FDToFILE(std::move(fd), "rb");
216     if (!scoped_file) {
217       perror("fdopen");
218       return false;
219     }
220     file = scoped_file.get();
221   }
222 
223   const size_t hex_size = EVP_MD_size(md) * 2;
224   char line[EVP_MAX_MD_SIZE * 2 + 2 /* spaces */ + PATH_MAX + 1 /* newline */ +
225             1 /* NUL */];
226   unsigned bad_lines = 0;
227   unsigned parsed_lines = 0;
228   unsigned error_lines = 0;
229   unsigned line_no = 0;
230   bool ok = true;
231   bool draining_overlong_line = false;
232 
233   for (;;) {
234     line_no++;
235 
236     if (fgets(line, sizeof(line), file) == nullptr) {
237       if (feof(file)) {
238         break;
239       }
240       fprintf(stderr, "Error reading from input.\n");
241       return false;
242     }
243 
244     size_t len = strlen(line);
245 
246     if (draining_overlong_line) {
247       if (line[len - 1] == '\n') {
248         draining_overlong_line = false;
249       }
250       continue;
251     }
252 
253     const bool overlong = line[len - 1] != '\n' && !feof(file);
254 
255     if (len < hex_size + 2 /* spaces */ + 1 /* filename */ ||
256         line[hex_size] != ' ' ||
257         line[hex_size + 1] != ' ' ||
258         overlong) {
259       bad_lines++;
260       if (args.warn) {
261         fprintf(stderr, "%s: %u: improperly formatted line\n",
262                 source.is_stdin() ? kStdinName : source.filename().c_str(), line_no);
263       }
264       if (args.strict) {
265         ok = false;
266       }
267       if (overlong) {
268         draining_overlong_line = true;
269       }
270       continue;
271     }
272 
273     if (line[len - 1] == '\n') {
274       line[len - 1] = 0;
275       len--;
276     }
277 
278     parsed_lines++;
279 
280     // coreutils does not attempt to restrict relative or absolute paths in the
281     // input so nor does this code.
282     std::string calculated_hex_digest;
283     const std::string target_filename(&line[hex_size + 2]);
284     Source target_source;
285     if (target_filename == "-") {
286       // coreutils reads from stdin if the filename is "-".
287       target_source = Source(Source::STDIN);
288     } else {
289       target_source = Source(target_filename);
290     }
291 
292     if (!SumFile(&calculated_hex_digest, md, target_source)) {
293       error_lines++;
294       ok = false;
295       continue;
296     }
297 
298     if (calculated_hex_digest != std::string(line, hex_size)) {
299       if (!args.status) {
300         printf("%s: FAILED\n", target_filename.c_str());
301       }
302       ok = false;
303       continue;
304     }
305 
306     if (!args.quiet) {
307       printf("%s: OK\n", target_filename.c_str());
308     }
309   }
310 
311   if (!args.status) {
312     if (bad_lines > 0 && parsed_lines > 0) {
313       fprintf(stderr, "WARNING: %u line%s improperly formatted\n", bad_lines,
314               bad_lines == 1 ? " is" : "s are");
315     }
316     if (error_lines > 0) {
317       fprintf(stderr, "WARNING: %u computed checksum(s) did NOT match\n",
318               error_lines);
319     }
320   }
321 
322   if (parsed_lines == 0) {
323     fprintf(stderr, "%s: no properly formatted checksum lines found.\n",
324             source.is_stdin() ? kStdinName : source.filename().c_str());
325     ok = false;
326   }
327 
328   return ok;
329 }
330 
331 // DigestSum acts like the coreutils *sum utilites, with the given hash
332 // function.
DigestSum(const EVP_MD * md,const std::vector<std::string> & args)333 static bool DigestSum(const EVP_MD *md,
334                       const std::vector<std::string> &args) {
335   bool check_mode = false;
336   CheckModeArguments check_args;
337   bool check_mode_args_given = false;
338   std::vector<Source> sources;
339 
340   auto it = args.begin();
341   while (it != args.end()) {
342     const std::string &arg = *it;
343     if (!arg.empty() && arg[0] != '-') {
344       break;
345     }
346 
347     it++;
348 
349     if (arg == "--") {
350       break;
351     }
352 
353     if (arg == "-") {
354       // "-" ends the argument list and indicates that stdin should be used.
355       sources.push_back(Source(Source::STDIN));
356       break;
357     }
358 
359     if (arg.size() >= 2 && arg[0] == '-' && arg[1] != '-') {
360       for (size_t i = 1; i < arg.size(); i++) {
361         switch (arg[i]) {
362           case 'b':
363           case 't':
364             // Binary/text mode – irrelevent, even on Windows.
365             break;
366           case 'c':
367             check_mode = true;
368             break;
369           case 'w':
370             check_mode_args_given = true;
371             check_args.warn = true;
372             break;
373           default:
374             fprintf(stderr, "Unknown option '%c'.\n", arg[i]);
375             return false;
376         }
377       }
378     } else if (arg == "--binary" || arg == "--text") {
379       // Binary/text mode – irrelevent, even on Windows.
380     } else if (arg == "--check") {
381       check_mode = true;
382     } else if (arg == "--quiet") {
383       check_mode_args_given = true;
384       check_args.quiet = true;
385     } else if (arg == "--status") {
386       check_mode_args_given = true;
387       check_args.status = true;
388     } else if (arg == "--warn") {
389       check_mode_args_given = true;
390       check_args.warn = true;
391     } else if (arg == "--strict") {
392       check_mode_args_given = true;
393       check_args.strict = true;
394     } else {
395       fprintf(stderr, "Unknown option '%s'.\n", arg.c_str());
396       return false;
397     }
398   }
399 
400   if (check_mode_args_given && !check_mode) {
401     fprintf(
402         stderr,
403         "Check mode arguments are only meaningful when verifying checksums.\n");
404     return false;
405   }
406 
407   for (; it != args.end(); it++) {
408     sources.push_back(Source(*it));
409   }
410 
411   if (sources.empty()) {
412     sources.push_back(Source(Source::STDIN));
413   }
414 
415   bool ok = true;
416 
417   if (check_mode) {
418     for (auto &source : sources) {
419       ok &= Check(check_args, md, source);
420     }
421   } else {
422     for (auto &source : sources) {
423       ok &= PrintFileSum(md, source);
424     }
425   }
426 
427   return ok;
428 }
429 
MD5Sum(const std::vector<std::string> & args)430 bool MD5Sum(const std::vector<std::string> &args) {
431   return DigestSum(EVP_md5(), args);
432 }
433 
SHA1Sum(const std::vector<std::string> & args)434 bool SHA1Sum(const std::vector<std::string> &args) {
435   return DigestSum(EVP_sha1(), args);
436 }
437 
SHA224Sum(const std::vector<std::string> & args)438 bool SHA224Sum(const std::vector<std::string> &args) {
439   return DigestSum(EVP_sha224(), args);
440 }
441 
SHA256Sum(const std::vector<std::string> & args)442 bool SHA256Sum(const std::vector<std::string> &args) {
443   return DigestSum(EVP_sha256(), args);
444 }
445 
SHA384Sum(const std::vector<std::string> & args)446 bool SHA384Sum(const std::vector<std::string> &args) {
447   return DigestSum(EVP_sha384(), args);
448 }
449 
SHA512Sum(const std::vector<std::string> & args)450 bool SHA512Sum(const std::vector<std::string> &args) {
451   return DigestSum(EVP_sha512(), args);
452 }
453 
SHA512256Sum(const std::vector<std::string> & args)454 bool SHA512256Sum(const std::vector<std::string> &args) {
455   return DigestSum(EVP_sha512_256(), args);
456 }
457