blob: e66f0132d4f1b87d58dcf8e4a9f3716225d5ca50 [file] [log] [blame]
Adam Langleycca4d592015-01-12 12:01:23 -08001/* 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
Adam Langleycca4d592015-01-12 12:01:23 -080017#include <memory>
18#include <string>
19#include <vector>
20
21#include <errno.h>
22#include <fcntl.h>
23#include <limits.h>
Matthew Braithwaiteb3488972016-10-19 15:05:29 -070024#include <stdio.h>
Adam Langleycca4d592015-01-12 12:01:23 -080025#include <sys/stat.h>
26#include <sys/types.h>
Brian Smithafdaeee2015-01-26 19:54:32 -080027
28#if !defined(OPENSSL_WINDOWS)
Brian Smith054e6822015-03-27 21:12:01 -100029#include <string.h>
Adam Langleycca4d592015-01-12 12:01:23 -080030#include <unistd.h>
Brian Smithafdaeee2015-01-26 19:54:32 -080031#if !defined(O_BINARY)
32#define O_BINARY 0
33#endif
34#else
David Benjamina353cdb2016-06-09 16:48:33 -040035OPENSSL_MSVC_PRAGMA(warning(push, 3))
Brian Smithafdaeee2015-01-26 19:54:32 -080036#include <windows.h>
David Benjamina353cdb2016-06-09 16:48:33 -040037OPENSSL_MSVC_PRAGMA(warning(pop))
Brian Smithafdaeee2015-01-26 19:54:32 -080038#include <io.h>
Jesko Jochumc1798542020-06-16 15:48:09 +020039#if !defined(PATH_MAX)
Brian Smithafdaeee2015-01-26 19:54:32 -080040#define PATH_MAX MAX_PATH
Brian Smithafdaeee2015-01-26 19:54:32 -080041#endif
Jesko Jochumc1798542020-06-16 15:48:09 +020042#endif
Adam Langleycca4d592015-01-12 12:01:23 -080043
44#include <openssl/digest.h>
45
Piotr Sikorac6d30292016-03-18 17:28:36 -070046#include "internal.h"
47
Adam Langley2b2d66d2015-01-30 17:08:37 -080048
Adam Langleycca4d592015-01-12 12:01:23 -080049// Source is an awkward expression of a union type in C++: Stdin | File filename.
50struct Source {
51 enum Type {
52 STDIN,
53 };
54
55 Source() : is_stdin_(false) {}
David Benjamin1e5ac5d2016-11-01 16:37:09 -040056 explicit Source(Type) : is_stdin_(true) {}
57 explicit Source(const std::string &name)
58 : is_stdin_(false), filename_(name) {}
Adam Langleycca4d592015-01-12 12:01:23 -080059
60 bool is_stdin() const { return is_stdin_; }
61 const std::string &filename() const { return filename_; }
62
63 private:
64 bool is_stdin_;
65 std::string filename_;
66};
67
68static const char kStdinName[] = "standard input";
69
David Benjamin8afdbf02020-06-16 14:06:07 -040070// OpenFile opens the regular file named |filename| and returns a file
71// descriptor to it.
72static ScopedFD OpenFile(const std::string &filename) {
73 ScopedFD fd = OpenFD(filename.c_str(), O_RDONLY | O_BINARY);
74 if (!fd) {
Adam Langleycca4d592015-01-12 12:01:23 -080075 fprintf(stderr, "Failed to open input file '%s': %s\n", filename.c_str(),
76 strerror(errno));
David Benjamin8afdbf02020-06-16 14:06:07 -040077 return ScopedFD();
Adam Langleycca4d592015-01-12 12:01:23 -080078 }
79
Brian Smithafdaeee2015-01-26 19:54:32 -080080#if !defined(OPENSSL_WINDOWS)
Adam Langleycca4d592015-01-12 12:01:23 -080081 struct stat st;
David Benjamin8afdbf02020-06-16 14:06:07 -040082 if (fstat(fd.get(), &st)) {
Adam Langleycca4d592015-01-12 12:01:23 -080083 fprintf(stderr, "Failed to stat input file '%s': %s\n", filename.c_str(),
84 strerror(errno));
David Benjamin8afdbf02020-06-16 14:06:07 -040085 return ScopedFD();
Adam Langleycca4d592015-01-12 12:01:23 -080086 }
87
88 if (!S_ISREG(st.st_mode)) {
89 fprintf(stderr, "%s: not a regular file\n", filename.c_str());
David Benjamin8afdbf02020-06-16 14:06:07 -040090 return ScopedFD();
Adam Langleycca4d592015-01-12 12:01:23 -080091 }
Brian Smithafdaeee2015-01-26 19:54:32 -080092#endif
Adam Langleycca4d592015-01-12 12:01:23 -080093
David Benjamin8afdbf02020-06-16 14:06:07 -040094 return fd;
Adam Langleycca4d592015-01-12 12:01:23 -080095}
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.
102static bool SumFile(std::string *out_hex, const EVP_MD *md,
103 const Source &source) {
David Benjamin8afdbf02020-06-16 14:06:07 -0400104 ScopedFD scoped_fd;
Adam Langleycca4d592015-01-12 12:01:23 -0800105 int fd;
106
107 if (source.is_stdin()) {
108 fd = 0;
109 } else {
David Benjamin8afdbf02020-06-16 14:06:07 -0400110 scoped_fd = OpenFile(source.filename());
111 if (!scoped_fd) {
Adam Langleycca4d592015-01-12 12:01:23 -0800112 return false;
113 }
David Benjamin8afdbf02020-06-16 14:06:07 -0400114 fd = scoped_fd.get();
Adam Langleycca4d592015-01-12 12:01:23 -0800115 }
116
117 static const size_t kBufSize = 8192;
David Benjamina36ac0a2023-07-04 12:48:02 -0400118 auto buf = std::make_unique<uint8_t[]>(kBufSize);
Adam Langleycca4d592015-01-12 12:01:23 -0800119
David Benjamin0cce8632016-10-20 15:13:26 -0400120 bssl::ScopedEVP_MD_CTX ctx;
121 if (!EVP_DigestInit_ex(ctx.get(), md, NULL)) {
Adam Langleycca4d592015-01-12 12:01:23 -0800122 fprintf(stderr, "Failed to initialize EVP_MD_CTX.\n");
123 return false;
124 }
125
126 for (;;) {
David Benjamin8afdbf02020-06-16 14:06:07 -0400127 size_t n;
128 if (!ReadFromFD(fd, &n, buf.get(), kBufSize)) {
Adam Langleycca4d592015-01-12 12:01:23 -0800129 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
David Benjamin8afdbf02020-06-16 14:06:07 -0400135 if (n == 0) {
136 break;
137 }
138
David Benjamin0cce8632016-10-20 15:13:26 -0400139 if (!EVP_DigestUpdate(ctx.get(), buf.get(), n)) {
Adam Langleycca4d592015-01-12 12:01:23 -0800140 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;
David Benjamin0cce8632016-10-20 15:13:26 -0400147 if (!EVP_DigestFinal_ex(ctx.get(), digest, &digest_len)) {
Adam Langleycca4d592015-01-12 12:01:23 -0800148 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.
167static 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
Brian Smithafdaeee2015-01-26 19:54:32 -0800173 // 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.
Adam Langleycca4d592015-01-12 12:01:23 -0800181 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.
188struct 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.
202static bool Check(const CheckModeArguments &args, const EVP_MD *md,
203 const Source &source) {
Adam Langleycca4d592015-01-12 12:01:23 -0800204 FILE *file;
David Benjamin8afdbf02020-06-16 14:06:07 -0400205 ScopedFILE scoped_file;
Adam Langleycca4d592015-01-12 12:01:23 -0800206
207 if (source.is_stdin()) {
208 file = stdin;
209 } else {
David Benjamin8afdbf02020-06-16 14:06:07 -0400210 ScopedFD fd = OpenFile(source.filename());
211 if (!fd) {
Adam Langleycca4d592015-01-12 12:01:23 -0800212 return false;
213 }
214
David Benjamin8afdbf02020-06-16 14:06:07 -0400215 scoped_file = FDToFILE(std::move(fd), "rb");
216 if (!scoped_file) {
Adam Langleycca4d592015-01-12 12:01:23 -0800217 perror("fdopen");
Adam Langleycca4d592015-01-12 12:01:23 -0800218 return false;
219 }
David Benjamin8afdbf02020-06-16 14:06:07 -0400220 file = scoped_file.get();
Adam Langleycca4d592015-01-12 12:01:23 -0800221 }
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;
Adam Langleycca4d592015-01-12 12:01:23 -0800229 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)) {
Adam Langleycca4d592015-01-12 12:01:23 -0800299 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.
333static 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
Adam Langleybed8f732015-01-26 16:34:37 -0800353 if (arg == "-") {
Adam Langleycca4d592015-01-12 12:01:23 -0800354 // "-" 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':
Brian Smithafdaeee2015-01-26 19:54:32 -0800364 // Binary/text mode – irrelevent, even on Windows.
Adam Langleycca4d592015-01-12 12:01:23 -0800365 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") {
Brian Smithafdaeee2015-01-26 19:54:32 -0800379 // Binary/text mode – irrelevent, even on Windows.
Adam Langleycca4d592015-01-12 12:01:23 -0800380 } 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
430bool MD5Sum(const std::vector<std::string> &args) {
431 return DigestSum(EVP_md5(), args);
432}
433
434bool SHA1Sum(const std::vector<std::string> &args) {
435 return DigestSum(EVP_sha1(), args);
436}
437
438bool SHA224Sum(const std::vector<std::string> &args) {
439 return DigestSum(EVP_sha224(), args);
440}
441
442bool SHA256Sum(const std::vector<std::string> &args) {
443 return DigestSum(EVP_sha256(), args);
444}
445
446bool SHA384Sum(const std::vector<std::string> &args) {
447 return DigestSum(EVP_sha384(), args);
448}
449
450bool SHA512Sum(const std::vector<std::string> &args) {
451 return DigestSum(EVP_sha512(), args);
452}
Adam Langley3c11bf52020-04-15 09:10:04 -0700453
454bool SHA512256Sum(const std::vector<std::string> &args) {
455 return DigestSum(EVP_sha512_256(), args);
456}