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 std::unique_ptr<uint8_t[]> buf(new 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 bad_hash_lines = 0;
230 unsigned line_no = 0;
231 bool ok = true;
232 bool draining_overlong_line = false;
233
234 for (;;) {
235 line_no++;
236
237 if (fgets(line, sizeof(line), file) == nullptr) {
238 if (feof(file)) {
239 break;
240 }
241 fprintf(stderr, "Error reading from input.\n");
242 return false;
243 }
244
245 size_t len = strlen(line);
246
247 if (draining_overlong_line) {
248 if (line[len - 1] == '\n') {
249 draining_overlong_line = false;
250 }
251 continue;
252 }
253
254 const bool overlong = line[len - 1] != '\n' && !feof(file);
255
256 if (len < hex_size + 2 /* spaces */ + 1 /* filename */ ||
257 line[hex_size] != ' ' ||
258 line[hex_size + 1] != ' ' ||
259 overlong) {
260 bad_lines++;
261 if (args.warn) {
262 fprintf(stderr, "%s: %u: improperly formatted line\n",
263 source.is_stdin() ? kStdinName : source.filename().c_str(), line_no);
264 }
265 if (args.strict) {
266 ok = false;
267 }
268 if (overlong) {
269 draining_overlong_line = true;
270 }
271 continue;
272 }
273
274 if (line[len - 1] == '\n') {
275 line[len - 1] = 0;
276 len--;
277 }
278
279 parsed_lines++;
280
281 // coreutils does not attempt to restrict relative or absolute paths in the
282 // input so nor does this code.
283 std::string calculated_hex_digest;
284 const std::string target_filename(&line[hex_size + 2]);
285 Source target_source;
286 if (target_filename == "-") {
287 // coreutils reads from stdin if the filename is "-".
288 target_source = Source(Source::STDIN);
289 } else {
290 target_source = Source(target_filename);
291 }
292
293 if (!SumFile(&calculated_hex_digest, md, target_source)) {
294 error_lines++;
295 ok = false;
296 continue;
297 }
298
299 if (calculated_hex_digest != std::string(line, hex_size)) {
300 bad_hash_lines++;
301 if (!args.status) {
302 printf("%s: FAILED\n", target_filename.c_str());
303 }
304 ok = false;
305 continue;
306 }
307
308 if (!args.quiet) {
309 printf("%s: OK\n", target_filename.c_str());
310 }
311 }
312
313 if (!args.status) {
314 if (bad_lines > 0 && parsed_lines > 0) {
315 fprintf(stderr, "WARNING: %u line%s improperly formatted\n", bad_lines,
316 bad_lines == 1 ? " is" : "s are");
317 }
318 if (error_lines > 0) {
319 fprintf(stderr, "WARNING: %u computed checksum(s) did NOT match\n",
320 error_lines);
321 }
322 }
323
324 if (parsed_lines == 0) {
325 fprintf(stderr, "%s: no properly formatted checksum lines found.\n",
326 source.is_stdin() ? kStdinName : source.filename().c_str());
327 ok = false;
328 }
329
330 return ok;
331 }
332
333 // DigestSum acts like the coreutils *sum utilites, with the given hash
334 // function.
DigestSum(const EVP_MD * md,const std::vector<std::string> & args)335 static bool DigestSum(const EVP_MD *md,
336 const std::vector<std::string> &args) {
337 bool check_mode = false;
338 CheckModeArguments check_args;
339 bool check_mode_args_given = false;
340 std::vector<Source> sources;
341
342 auto it = args.begin();
343 while (it != args.end()) {
344 const std::string &arg = *it;
345 if (!arg.empty() && arg[0] != '-') {
346 break;
347 }
348
349 it++;
350
351 if (arg == "--") {
352 break;
353 }
354
355 if (arg == "-") {
356 // "-" ends the argument list and indicates that stdin should be used.
357 sources.push_back(Source(Source::STDIN));
358 break;
359 }
360
361 if (arg.size() >= 2 && arg[0] == '-' && arg[1] != '-') {
362 for (size_t i = 1; i < arg.size(); i++) {
363 switch (arg[i]) {
364 case 'b':
365 case 't':
366 // Binary/text mode – irrelevent, even on Windows.
367 break;
368 case 'c':
369 check_mode = true;
370 break;
371 case 'w':
372 check_mode_args_given = true;
373 check_args.warn = true;
374 break;
375 default:
376 fprintf(stderr, "Unknown option '%c'.\n", arg[i]);
377 return false;
378 }
379 }
380 } else if (arg == "--binary" || arg == "--text") {
381 // Binary/text mode – irrelevent, even on Windows.
382 } else if (arg == "--check") {
383 check_mode = true;
384 } else if (arg == "--quiet") {
385 check_mode_args_given = true;
386 check_args.quiet = true;
387 } else if (arg == "--status") {
388 check_mode_args_given = true;
389 check_args.status = true;
390 } else if (arg == "--warn") {
391 check_mode_args_given = true;
392 check_args.warn = true;
393 } else if (arg == "--strict") {
394 check_mode_args_given = true;
395 check_args.strict = true;
396 } else {
397 fprintf(stderr, "Unknown option '%s'.\n", arg.c_str());
398 return false;
399 }
400 }
401
402 if (check_mode_args_given && !check_mode) {
403 fprintf(
404 stderr,
405 "Check mode arguments are only meaningful when verifying checksums.\n");
406 return false;
407 }
408
409 for (; it != args.end(); it++) {
410 sources.push_back(Source(*it));
411 }
412
413 if (sources.empty()) {
414 sources.push_back(Source(Source::STDIN));
415 }
416
417 bool ok = true;
418
419 if (check_mode) {
420 for (auto &source : sources) {
421 ok &= Check(check_args, md, source);
422 }
423 } else {
424 for (auto &source : sources) {
425 ok &= PrintFileSum(md, source);
426 }
427 }
428
429 return ok;
430 }
431
MD5Sum(const std::vector<std::string> & args)432 bool MD5Sum(const std::vector<std::string> &args) {
433 return DigestSum(EVP_md5(), args);
434 }
435
SHA1Sum(const std::vector<std::string> & args)436 bool SHA1Sum(const std::vector<std::string> &args) {
437 return DigestSum(EVP_sha1(), args);
438 }
439
SHA224Sum(const std::vector<std::string> & args)440 bool SHA224Sum(const std::vector<std::string> &args) {
441 return DigestSum(EVP_sha224(), args);
442 }
443
SHA256Sum(const std::vector<std::string> & args)444 bool SHA256Sum(const std::vector<std::string> &args) {
445 return DigestSum(EVP_sha256(), args);
446 }
447
SHA384Sum(const std::vector<std::string> & args)448 bool SHA384Sum(const std::vector<std::string> &args) {
449 return DigestSum(EVP_sha384(), args);
450 }
451
SHA512Sum(const std::vector<std::string> & args)452 bool SHA512Sum(const std::vector<std::string> &args) {
453 return DigestSum(EVP_sha512(), args);
454 }
455
SHA512256Sum(const std::vector<std::string> & args)456 bool SHA512256Sum(const std::vector<std::string> &args) {
457 return DigestSum(EVP_sha512_256(), args);
458 }
459