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