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