blob: 2e3e6085f18e0a27c8cd1a86415af2c1394b5dd5 [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>
Adam Langleycca4d592015-01-12 12:01:23 -080024#include <sys/stat.h>
25#include <sys/types.h>
Brian Smithafdaeee2015-01-26 19:54:32 -080026
27#if !defined(OPENSSL_WINDOWS)
Brian Smith054e6822015-03-27 21:12:01 -100028#include <string.h>
Adam Langleycca4d592015-01-12 12:01:23 -080029#include <unistd.h>
Brian Smithafdaeee2015-01-26 19:54:32 -080030#if !defined(O_BINARY)
31#define O_BINARY 0
32#endif
33#else
David Benjamina353cdb2016-06-09 16:48:33 -040034OPENSSL_MSVC_PRAGMA(warning(push, 3))
Brian Smithafdaeee2015-01-26 19:54:32 -080035#include <windows.h>
David Benjamina353cdb2016-06-09 16:48:33 -040036OPENSSL_MSVC_PRAGMA(warning(pop))
Brian Smithafdaeee2015-01-26 19:54:32 -080037#include <io.h>
38#define PATH_MAX MAX_PATH
Brian Smith33970e62015-01-27 22:32:08 -080039typedef int ssize_t;
Brian Smithafdaeee2015-01-26 19:54:32 -080040#endif
Adam Langleycca4d592015-01-12 12:01:23 -080041
42#include <openssl/digest.h>
43
Piotr Sikorac6d30292016-03-18 17:28:36 -070044#include "internal.h"
45
Adam Langley2b2d66d2015-01-30 17:08:37 -080046
Adam Langleycca4d592015-01-12 12:01:23 -080047struct close_delete {
48 void operator()(int *fd) {
nmittlerf0322b22016-05-19 08:49:59 -070049 BORINGSSL_CLOSE(*fd);
Adam Langleycca4d592015-01-12 12:01:23 -080050 }
51};
52
53template<typename T, typename R, R (*func) (T*)>
54struct func_delete {
55 void operator()(T* obj) {
56 func(obj);
57 }
58};
59
60// Source is an awkward expression of a union type in C++: Stdin | File filename.
61struct Source {
62 enum Type {
63 STDIN,
64 };
65
66 Source() : is_stdin_(false) {}
67 Source(Type) : is_stdin_(true) {}
68 Source(const std::string &name) : is_stdin_(false), filename_(name) {}
69
70 bool is_stdin() const { return is_stdin_; }
71 const std::string &filename() const { return filename_; }
72
73 private:
74 bool is_stdin_;
75 std::string filename_;
76};
77
78static const char kStdinName[] = "standard input";
79
80// OpenFile opens the regular file named |filename| and sets |*out_fd| to be a
81// file descriptor to it. Returns true on sucess or prints an error to stderr
82// and returns false on error.
83static bool OpenFile(int *out_fd, const std::string &filename) {
84 *out_fd = -1;
85
nmittlerf0322b22016-05-19 08:49:59 -070086 int fd = BORINGSSL_OPEN(filename.c_str(), O_RDONLY | O_BINARY);
Adam Langleycca4d592015-01-12 12:01:23 -080087 if (fd < 0) {
88 fprintf(stderr, "Failed to open input file '%s': %s\n", filename.c_str(),
89 strerror(errno));
90 return false;
91 }
Brian Smithdd973b22015-03-17 20:50:09 -100092 std::unique_ptr<int, close_delete> scoped_fd(&fd);
Adam Langleycca4d592015-01-12 12:01:23 -080093
Brian Smithafdaeee2015-01-26 19:54:32 -080094#if !defined(OPENSSL_WINDOWS)
Adam Langleycca4d592015-01-12 12:01:23 -080095 struct stat st;
96 if (fstat(fd, &st)) {
97 fprintf(stderr, "Failed to stat input file '%s': %s\n", filename.c_str(),
98 strerror(errno));
Brian Smithdd973b22015-03-17 20:50:09 -100099 return false;
Adam Langleycca4d592015-01-12 12:01:23 -0800100 }
101
102 if (!S_ISREG(st.st_mode)) {
103 fprintf(stderr, "%s: not a regular file\n", filename.c_str());
Brian Smithdd973b22015-03-17 20:50:09 -1000104 return false;
Adam Langleycca4d592015-01-12 12:01:23 -0800105 }
Brian Smithafdaeee2015-01-26 19:54:32 -0800106#endif
Adam Langleycca4d592015-01-12 12:01:23 -0800107
108 *out_fd = fd;
Brian Smithdd973b22015-03-17 20:50:09 -1000109 scoped_fd.release();
Adam Langleycca4d592015-01-12 12:01:23 -0800110 return true;
Adam Langleycca4d592015-01-12 12:01:23 -0800111}
112
113// SumFile hashes the contents of |source| with |md| and sets |*out_hex| to the
114// hex-encoded result.
115//
116// It returns true on success or prints an error to stderr and returns false on
117// error.
118static bool SumFile(std::string *out_hex, const EVP_MD *md,
119 const Source &source) {
120 std::unique_ptr<int, close_delete> scoped_fd;
121 int fd;
122
123 if (source.is_stdin()) {
124 fd = 0;
125 } else {
126 if (!OpenFile(&fd, source.filename())) {
127 return false;
128 }
129 scoped_fd.reset(&fd);
130 }
131
132 static const size_t kBufSize = 8192;
133 std::unique_ptr<uint8_t[]> buf(new uint8_t[kBufSize]);
134
135 EVP_MD_CTX ctx;
136 EVP_MD_CTX_init(&ctx);
137 std::unique_ptr<EVP_MD_CTX, func_delete<EVP_MD_CTX, int, EVP_MD_CTX_cleanup>>
138 scoped_ctx(&ctx);
139
140 if (!EVP_DigestInit_ex(&ctx, md, NULL)) {
141 fprintf(stderr, "Failed to initialize EVP_MD_CTX.\n");
142 return false;
143 }
144
145 for (;;) {
146 ssize_t n;
147
148 do {
nmittlerf0322b22016-05-19 08:49:59 -0700149 n = BORINGSSL_READ(fd, buf.get(), kBufSize);
Adam Langleycca4d592015-01-12 12:01:23 -0800150 } while (n == -1 && errno == EINTR);
151
152 if (n == 0) {
153 break;
154 } else if (n < 0) {
155 fprintf(stderr, "Failed to read from %s: %s\n",
156 source.is_stdin() ? kStdinName : source.filename().c_str(),
157 strerror(errno));
158 return false;
159 }
160
161 if (!EVP_DigestUpdate(&ctx, buf.get(), n)) {
162 fprintf(stderr, "Failed to update hash.\n");
163 return false;
164 }
165 }
166
167 uint8_t digest[EVP_MAX_MD_SIZE];
168 unsigned digest_len;
169 if (!EVP_DigestFinal_ex(&ctx, digest, &digest_len)) {
170 fprintf(stderr, "Failed to finish hash.\n");
171 return false;
172 }
173
174 char hex_digest[EVP_MAX_MD_SIZE * 2];
175 static const char kHextable[] = "0123456789abcdef";
176 for (unsigned i = 0; i < digest_len; i++) {
177 const uint8_t b = digest[i];
178 hex_digest[i * 2] = kHextable[b >> 4];
179 hex_digest[i * 2 + 1] = kHextable[b & 0xf];
180 }
181 *out_hex = std::string(hex_digest, digest_len * 2);
182
183 return true;
184}
185
186// PrintFileSum hashes |source| with |md| and prints a line to stdout in the
187// format of the coreutils *sum utilities. It returns true on success or prints
188// an error to stderr and returns false on error.
189static bool PrintFileSum(const EVP_MD *md, const Source &source) {
190 std::string hex_digest;
191 if (!SumFile(&hex_digest, md, source)) {
192 return false;
193 }
194
Brian Smithafdaeee2015-01-26 19:54:32 -0800195 // TODO: When given "--binary" or "-b", we should print " *" instead of " "
196 // between the digest and the filename.
197 //
198 // MSYS and Cygwin md5sum default to binary mode by default, whereas other
199 // platforms' tools default to text mode by default. We default to text mode
200 // by default and consider text mode equivalent to binary mode (i.e. we
201 // always use Unix semantics, even on Windows), which means that our default
202 // output will differ from the MSYS and Cygwin tools' default output.
Adam Langleycca4d592015-01-12 12:01:23 -0800203 printf("%s %s\n", hex_digest.c_str(),
204 source.is_stdin() ? "-" : source.filename().c_str());
205 return true;
206}
207
208// CheckModeArguments contains arguments for the check mode. See the
209// sha256sum(1) man page for details.
210struct CheckModeArguments {
211 bool quiet = false;
212 bool status = false;
213 bool warn = false;
214 bool strict = false;
215};
216
217// Check reads lines from |source| where each line is in the format of the
218// coreutils *sum utilities. It attempts to verify each hash by reading the
219// file named in the line.
220//
221// It returns true if all files were verified and, if |args.strict|, no input
222// lines had formatting errors. Otherwise it prints errors to stderr and
223// returns false.
224static bool Check(const CheckModeArguments &args, const EVP_MD *md,
225 const Source &source) {
226 std::unique_ptr<FILE, func_delete<FILE, int, fclose>> scoped_file;
227 FILE *file;
228
229 if (source.is_stdin()) {
230 file = stdin;
231 } else {
232 int fd;
233 if (!OpenFile(&fd, source.filename())) {
234 return false;
235 }
236
nmittlerf0322b22016-05-19 08:49:59 -0700237 file = BORINGSSL_FDOPEN(fd, "rb");
Adam Langleycca4d592015-01-12 12:01:23 -0800238 if (!file) {
239 perror("fdopen");
nmittlerf0322b22016-05-19 08:49:59 -0700240 BORINGSSL_CLOSE(fd);
Adam Langleycca4d592015-01-12 12:01:23 -0800241 return false;
242 }
243
244 scoped_file = std::unique_ptr<FILE, func_delete<FILE, int, fclose>>(file);
245 }
246
247 const size_t hex_size = EVP_MD_size(md) * 2;
248 char line[EVP_MAX_MD_SIZE * 2 + 2 /* spaces */ + PATH_MAX + 1 /* newline */ +
249 1 /* NUL */];
250 unsigned bad_lines = 0;
251 unsigned parsed_lines = 0;
252 unsigned error_lines = 0;
253 unsigned bad_hash_lines = 0;
254 unsigned line_no = 0;
255 bool ok = true;
256 bool draining_overlong_line = false;
257
258 for (;;) {
259 line_no++;
260
261 if (fgets(line, sizeof(line), file) == nullptr) {
262 if (feof(file)) {
263 break;
264 }
265 fprintf(stderr, "Error reading from input.\n");
266 return false;
267 }
268
269 size_t len = strlen(line);
270
271 if (draining_overlong_line) {
272 if (line[len - 1] == '\n') {
273 draining_overlong_line = false;
274 }
275 continue;
276 }
277
278 const bool overlong = line[len - 1] != '\n' && !feof(file);
279
280 if (len < hex_size + 2 /* spaces */ + 1 /* filename */ ||
281 line[hex_size] != ' ' ||
282 line[hex_size + 1] != ' ' ||
283 overlong) {
284 bad_lines++;
285 if (args.warn) {
286 fprintf(stderr, "%s: %u: improperly formatted line\n",
287 source.is_stdin() ? kStdinName : source.filename().c_str(), line_no);
288 }
289 if (args.strict) {
290 ok = false;
291 }
292 if (overlong) {
293 draining_overlong_line = true;
294 }
295 continue;
296 }
297
298 if (line[len - 1] == '\n') {
299 line[len - 1] = 0;
300 len--;
301 }
302
303 parsed_lines++;
304
305 // coreutils does not attempt to restrict relative or absolute paths in the
306 // input so nor does this code.
307 std::string calculated_hex_digest;
308 const std::string target_filename(&line[hex_size + 2]);
309 Source target_source;
310 if (target_filename == "-") {
311 // coreutils reads from stdin if the filename is "-".
312 target_source = Source(Source::STDIN);
313 } else {
314 target_source = Source(target_filename);
315 }
316
317 if (!SumFile(&calculated_hex_digest, md, target_source)) {
318 error_lines++;
319 ok = false;
320 continue;
321 }
322
323 if (calculated_hex_digest != std::string(line, hex_size)) {
324 bad_hash_lines++;
325 if (!args.status) {
326 printf("%s: FAILED\n", target_filename.c_str());
327 }
328 ok = false;
329 continue;
330 }
331
332 if (!args.quiet) {
333 printf("%s: OK\n", target_filename.c_str());
334 }
335 }
336
337 if (!args.status) {
338 if (bad_lines > 0 && parsed_lines > 0) {
339 fprintf(stderr, "WARNING: %u line%s improperly formatted\n", bad_lines,
340 bad_lines == 1 ? " is" : "s are");
341 }
342 if (error_lines > 0) {
343 fprintf(stderr, "WARNING: %u computed checksum(s) did NOT match\n",
344 error_lines);
345 }
346 }
347
348 if (parsed_lines == 0) {
349 fprintf(stderr, "%s: no properly formatted checksum lines found.\n",
350 source.is_stdin() ? kStdinName : source.filename().c_str());
351 ok = false;
352 }
353
354 return ok;
355}
356
357// DigestSum acts like the coreutils *sum utilites, with the given hash
358// function.
359static bool DigestSum(const EVP_MD *md,
360 const std::vector<std::string> &args) {
361 bool check_mode = false;
362 CheckModeArguments check_args;
363 bool check_mode_args_given = false;
364 std::vector<Source> sources;
365
366 auto it = args.begin();
367 while (it != args.end()) {
368 const std::string &arg = *it;
369 if (!arg.empty() && arg[0] != '-') {
370 break;
371 }
372
373 it++;
374
375 if (arg == "--") {
376 break;
377 }
378
Adam Langleybed8f732015-01-26 16:34:37 -0800379 if (arg == "-") {
Adam Langleycca4d592015-01-12 12:01:23 -0800380 // "-" ends the argument list and indicates that stdin should be used.
381 sources.push_back(Source(Source::STDIN));
382 break;
383 }
384
385 if (arg.size() >= 2 && arg[0] == '-' && arg[1] != '-') {
386 for (size_t i = 1; i < arg.size(); i++) {
387 switch (arg[i]) {
388 case 'b':
389 case 't':
Brian Smithafdaeee2015-01-26 19:54:32 -0800390 // Binary/text mode – irrelevent, even on Windows.
Adam Langleycca4d592015-01-12 12:01:23 -0800391 break;
392 case 'c':
393 check_mode = true;
394 break;
395 case 'w':
396 check_mode_args_given = true;
397 check_args.warn = true;
398 break;
399 default:
400 fprintf(stderr, "Unknown option '%c'.\n", arg[i]);
401 return false;
402 }
403 }
404 } else if (arg == "--binary" || arg == "--text") {
Brian Smithafdaeee2015-01-26 19:54:32 -0800405 // Binary/text mode – irrelevent, even on Windows.
Adam Langleycca4d592015-01-12 12:01:23 -0800406 } else if (arg == "--check") {
407 check_mode = true;
408 } else if (arg == "--quiet") {
409 check_mode_args_given = true;
410 check_args.quiet = true;
411 } else if (arg == "--status") {
412 check_mode_args_given = true;
413 check_args.status = true;
414 } else if (arg == "--warn") {
415 check_mode_args_given = true;
416 check_args.warn = true;
417 } else if (arg == "--strict") {
418 check_mode_args_given = true;
419 check_args.strict = true;
420 } else {
421 fprintf(stderr, "Unknown option '%s'.\n", arg.c_str());
422 return false;
423 }
424 }
425
426 if (check_mode_args_given && !check_mode) {
427 fprintf(
428 stderr,
429 "Check mode arguments are only meaningful when verifying checksums.\n");
430 return false;
431 }
432
433 for (; it != args.end(); it++) {
434 sources.push_back(Source(*it));
435 }
436
437 if (sources.empty()) {
438 sources.push_back(Source(Source::STDIN));
439 }
440
441 bool ok = true;
442
443 if (check_mode) {
444 for (auto &source : sources) {
445 ok &= Check(check_args, md, source);
446 }
447 } else {
448 for (auto &source : sources) {
449 ok &= PrintFileSum(md, source);
450 }
451 }
452
453 return ok;
454}
455
456bool MD5Sum(const std::vector<std::string> &args) {
457 return DigestSum(EVP_md5(), args);
458}
459
460bool SHA1Sum(const std::vector<std::string> &args) {
461 return DigestSum(EVP_sha1(), args);
462}
463
464bool SHA224Sum(const std::vector<std::string> &args) {
465 return DigestSum(EVP_sha224(), args);
466}
467
468bool SHA256Sum(const std::vector<std::string> &args) {
469 return DigestSum(EVP_sha256(), args);
470}
471
472bool SHA384Sum(const std::vector<std::string> &args) {
473 return DigestSum(EVP_sha384(), args);
474}
475
476bool SHA512Sum(const std::vector<std::string> &args) {
477 return DigestSum(EVP_sha512(), args);
478}