| // Copyright 2014 The BoringSSL Authors | |
| // | |
| // Licensed under the Apache License, Version 2.0 (the "License"); | |
| // you may not use this file except in compliance with the License. | |
| // You may obtain a copy of the License at | |
| // | |
| // https://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, software | |
| // distributed under the License is distributed on an "AS IS" BASIS, | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| // See the License for the specific language governing permissions and | |
| // limitations under the License. | |
| #include <openssl/base.h> | |
| #include <memory> | |
| #include <string> | |
| #include <vector> | |
| #include <errno.h> | |
| #include <fcntl.h> | |
| #include <limits.h> | |
| #include <stdio.h> | |
| #include <sys/stat.h> | |
| #include <sys/types.h> | |
| #if !defined(OPENSSL_WINDOWS) | |
| #include <string.h> | |
| #include <unistd.h> | |
| #if !defined(O_BINARY) | |
| #define O_BINARY 0 | |
| #endif | |
| #else | |
| #include <windows.h> | |
| #include <io.h> | |
| #if !defined(PATH_MAX) | |
| #define PATH_MAX MAX_PATH | |
| #endif | |
| #endif | |
| #include <openssl/digest.h> | |
| #include "internal.h" | |
| BSSL_NAMESPACE_BEGIN | |
| // Source is an awkward expression of a union type in C++: Stdin | File | |
| // filename. | |
| struct Source { | |
| enum Type { | |
| STDIN, | |
| }; | |
| Source() : is_stdin_(false) {} | |
| explicit Source(Type) : is_stdin_(true) {} | |
| explicit Source(const std::string &name) | |
| : is_stdin_(false), filename_(name) {} | |
| bool is_stdin() const { return is_stdin_; } | |
| const std::string &filename() const { return filename_; } | |
| private: | |
| bool is_stdin_; | |
| std::string filename_; | |
| }; | |
| static const char kStdinName[] = "standard input"; | |
| // OpenFile opens the regular file named `filename` and returns a file | |
| // descriptor to it. | |
| static ScopedFD OpenFile(const std::string &filename) { | |
| ScopedFD fd = OpenFD(filename.c_str(), O_RDONLY | O_BINARY); | |
| if (!fd) { | |
| fprintf(stderr, "Failed to open input file '%s': %s\n", filename.c_str(), | |
| strerror(errno)); | |
| return ScopedFD(); | |
| } | |
| #if !defined(OPENSSL_WINDOWS) | |
| struct stat st; | |
| if (fstat(fd.get(), &st)) { | |
| fprintf(stderr, "Failed to stat input file '%s': %s\n", filename.c_str(), | |
| strerror(errno)); | |
| return ScopedFD(); | |
| } | |
| if (!S_ISREG(st.st_mode)) { | |
| fprintf(stderr, "%s: not a regular file\n", filename.c_str()); | |
| return ScopedFD(); | |
| } | |
| #endif | |
| return fd; | |
| } | |
| // SumFile hashes the contents of `source` with `md` and sets `*out_hex` to the | |
| // hex-encoded result. | |
| // | |
| // It returns true on success or prints an error to stderr and returns false on | |
| // error. | |
| static bool SumFile(std::string *out_hex, const EVP_MD *md, | |
| const Source &source) { | |
| ScopedFD scoped_fd; | |
| int fd; | |
| if (source.is_stdin()) { | |
| fd = 0; | |
| } else { | |
| scoped_fd = OpenFile(source.filename()); | |
| if (!scoped_fd) { | |
| return false; | |
| } | |
| fd = scoped_fd.get(); | |
| } | |
| static const size_t kBufSize = 8192; | |
| auto buf = std::make_unique<uint8_t[]>(kBufSize); | |
| bssl::ScopedEVP_MD_CTX ctx; | |
| if (!EVP_DigestInit_ex(ctx.get(), md, NULL)) { | |
| fprintf(stderr, "Failed to initialize EVP_MD_CTX.\n"); | |
| return false; | |
| } | |
| for (;;) { | |
| size_t n; | |
| if (!ReadFromFD(fd, &n, buf.get(), kBufSize)) { | |
| fprintf(stderr, "Failed to read from %s: %s\n", | |
| source.is_stdin() ? kStdinName : source.filename().c_str(), | |
| strerror(errno)); | |
| return false; | |
| } | |
| if (n == 0) { | |
| break; | |
| } | |
| if (!EVP_DigestUpdate(ctx.get(), buf.get(), n)) { | |
| fprintf(stderr, "Failed to update hash.\n"); | |
| return false; | |
| } | |
| } | |
| uint8_t digest[EVP_MAX_MD_SIZE]; | |
| unsigned digest_len; | |
| if (!EVP_DigestFinal_ex(ctx.get(), digest, &digest_len)) { | |
| fprintf(stderr, "Failed to finish hash.\n"); | |
| return false; | |
| } | |
| char hex_digest[EVP_MAX_MD_SIZE * 2]; | |
| static const char kHextable[] = "0123456789abcdef"; | |
| for (unsigned i = 0; i < digest_len; i++) { | |
| const uint8_t b = digest[i]; | |
| hex_digest[i * 2] = kHextable[b >> 4]; | |
| hex_digest[i * 2 + 1] = kHextable[b & 0xf]; | |
| } | |
| *out_hex = std::string(hex_digest, digest_len * 2); | |
| return true; | |
| } | |
| // PrintFileSum hashes `source` with `md` and prints a line to stdout in the | |
| // format of the coreutils *sum utilities. It returns true on success or prints | |
| // an error to stderr and returns false on error. | |
| static bool PrintFileSum(const EVP_MD *md, const Source &source) { | |
| std::string hex_digest; | |
| if (!SumFile(&hex_digest, md, source)) { | |
| return false; | |
| } | |
| // TODO: When given "--binary" or "-b", we should print " *" instead of " " | |
| // between the digest and the filename. | |
| // | |
| // MSYS and Cygwin md5sum default to binary mode by default, whereas other | |
| // platforms' tools default to text mode by default. We default to text mode | |
| // by default and consider text mode equivalent to binary mode (i.e. we | |
| // always use Unix semantics, even on Windows), which means that our default | |
| // output will differ from the MSYS and Cygwin tools' default output. | |
| printf("%s %s\n", hex_digest.c_str(), | |
| source.is_stdin() ? "-" : source.filename().c_str()); | |
| return true; | |
| } | |
| // CheckModeArguments contains arguments for the check mode. See the | |
| // sha256sum(1) man page for details. | |
| struct CheckModeArguments { | |
| bool quiet = false; | |
| bool status = false; | |
| bool warn = false; | |
| bool strict = false; | |
| }; | |
| // Check reads lines from `source` where each line is in the format of the | |
| // coreutils *sum utilities. It attempts to verify each hash by reading the | |
| // file named in the line. | |
| // | |
| // It returns true if all files were verified and, if `args.strict`, no input | |
| // lines had formatting errors. Otherwise it prints errors to stderr and | |
| // returns false. | |
| static bool Check(const CheckModeArguments &args, const EVP_MD *md, | |
| const Source &source) { | |
| FILE *file; | |
| ScopedFILE scoped_file; | |
| if (source.is_stdin()) { | |
| file = stdin; | |
| } else { | |
| ScopedFD fd = OpenFile(source.filename()); | |
| if (!fd) { | |
| return false; | |
| } | |
| scoped_file = FDToFILE(std::move(fd), "rb"); | |
| if (!scoped_file) { | |
| perror("fdopen"); | |
| return false; | |
| } | |
| file = scoped_file.get(); | |
| } | |
| const size_t hex_size = EVP_MD_size(md) * 2; | |
| char line[EVP_MAX_MD_SIZE * 2 + 2 /* spaces */ + PATH_MAX + 1 /* newline */ + | |
| 1 /* NUL */]; | |
| unsigned bad_lines = 0; | |
| unsigned parsed_lines = 0; | |
| unsigned error_lines = 0; | |
| unsigned line_no = 0; | |
| bool ok = true; | |
| bool draining_overlong_line = false; | |
| for (;;) { | |
| line_no++; | |
| if (fgets(line, sizeof(line), file) == nullptr) { | |
| if (feof(file)) { | |
| break; | |
| } | |
| fprintf(stderr, "Error reading from input.\n"); | |
| return false; | |
| } | |
| size_t len = strlen(line); | |
| if (draining_overlong_line) { | |
| if (line[len - 1] == '\n') { | |
| draining_overlong_line = false; | |
| } | |
| continue; | |
| } | |
| const bool overlong = line[len - 1] != '\n' && !feof(file); | |
| if (len < hex_size + 2 /* spaces */ + 1 /* filename */ || | |
| line[hex_size] != ' ' || | |
| line[hex_size + 1] != ' ' || | |
| overlong) { | |
| bad_lines++; | |
| if (args.warn) { | |
| fprintf(stderr, "%s: %u: improperly formatted line\n", | |
| source.is_stdin() ? kStdinName : source.filename().c_str(), line_no); | |
| } | |
| if (args.strict) { | |
| ok = false; | |
| } | |
| if (overlong) { | |
| draining_overlong_line = true; | |
| } | |
| continue; | |
| } | |
| if (line[len - 1] == '\n') { | |
| line[len - 1] = 0; | |
| len--; | |
| } | |
| parsed_lines++; | |
| // coreutils does not attempt to restrict relative or absolute paths in the | |
| // input so nor does this code. | |
| std::string calculated_hex_digest; | |
| const std::string target_filename(&line[hex_size + 2]); | |
| Source target_source; | |
| if (target_filename == "-") { | |
| // coreutils reads from stdin if the filename is "-". | |
| target_source = Source(Source::STDIN); | |
| } else { | |
| target_source = Source(target_filename); | |
| } | |
| if (!SumFile(&calculated_hex_digest, md, target_source)) { | |
| error_lines++; | |
| ok = false; | |
| continue; | |
| } | |
| if (calculated_hex_digest != std::string(line, hex_size)) { | |
| if (!args.status) { | |
| printf("%s: FAILED\n", target_filename.c_str()); | |
| } | |
| ok = false; | |
| continue; | |
| } | |
| if (!args.quiet) { | |
| printf("%s: OK\n", target_filename.c_str()); | |
| } | |
| } | |
| if (!args.status) { | |
| if (bad_lines > 0 && parsed_lines > 0) { | |
| fprintf(stderr, "WARNING: %u line%s improperly formatted\n", bad_lines, | |
| bad_lines == 1 ? " is" : "s are"); | |
| } | |
| if (error_lines > 0) { | |
| fprintf(stderr, "WARNING: %u computed checksum(s) did NOT match\n", | |
| error_lines); | |
| } | |
| } | |
| if (parsed_lines == 0) { | |
| fprintf(stderr, "%s: no properly formatted checksum lines found.\n", | |
| source.is_stdin() ? kStdinName : source.filename().c_str()); | |
| ok = false; | |
| } | |
| return ok; | |
| } | |
| // DigestSum acts like the coreutils *sum utilities, with the given hash | |
| // function. | |
| static bool DigestSum(const EVP_MD *md, | |
| const std::vector<std::string> &args) { | |
| bool check_mode = false; | |
| CheckModeArguments check_args; | |
| bool check_mode_args_given = false; | |
| std::vector<Source> sources; | |
| auto it = args.begin(); | |
| while (it != args.end()) { | |
| const std::string &arg = *it; | |
| if (!arg.empty() && arg[0] != '-') { | |
| break; | |
| } | |
| it++; | |
| if (arg == "--") { | |
| break; | |
| } | |
| if (arg == "-") { | |
| // "-" ends the argument list and indicates that stdin should be used. | |
| sources.push_back(Source(Source::STDIN)); | |
| break; | |
| } | |
| if (arg.size() >= 2 && arg[0] == '-' && arg[1] != '-') { | |
| for (size_t i = 1; i < arg.size(); i++) { | |
| switch (arg[i]) { | |
| case 'b': | |
| case 't': | |
| // Binary/text mode – irrelevant, even on Windows. | |
| break; | |
| case 'c': | |
| check_mode = true; | |
| break; | |
| case 'w': | |
| check_mode_args_given = true; | |
| check_args.warn = true; | |
| break; | |
| default: | |
| fprintf(stderr, "Unknown option '%c'.\n", arg[i]); | |
| return false; | |
| } | |
| } | |
| } else if (arg == "--binary" || arg == "--text") { | |
| // Binary/text mode – irrelevant, even on Windows. | |
| } else if (arg == "--check") { | |
| check_mode = true; | |
| } else if (arg == "--quiet") { | |
| check_mode_args_given = true; | |
| check_args.quiet = true; | |
| } else if (arg == "--status") { | |
| check_mode_args_given = true; | |
| check_args.status = true; | |
| } else if (arg == "--warn") { | |
| check_mode_args_given = true; | |
| check_args.warn = true; | |
| } else if (arg == "--strict") { | |
| check_mode_args_given = true; | |
| check_args.strict = true; | |
| } else { | |
| fprintf(stderr, "Unknown option '%s'.\n", arg.c_str()); | |
| return false; | |
| } | |
| } | |
| if (check_mode_args_given && !check_mode) { | |
| fprintf( | |
| stderr, | |
| "Check mode arguments are only meaningful when verifying checksums.\n"); | |
| return false; | |
| } | |
| for (; it != args.end(); it++) { | |
| sources.push_back(Source(*it)); | |
| } | |
| if (sources.empty()) { | |
| sources.push_back(Source(Source::STDIN)); | |
| } | |
| bool ok = true; | |
| if (check_mode) { | |
| for (auto &source : sources) { | |
| ok &= Check(check_args, md, source); | |
| } | |
| } else { | |
| for (auto &source : sources) { | |
| ok &= PrintFileSum(md, source); | |
| } | |
| } | |
| return ok; | |
| } | |
| bool MD5Sum(const std::vector<std::string> &args) { | |
| return DigestSum(EVP_md5(), args); | |
| } | |
| bool SHA1Sum(const std::vector<std::string> &args) { | |
| return DigestSum(EVP_sha1(), args); | |
| } | |
| bool SHA224Sum(const std::vector<std::string> &args) { | |
| return DigestSum(EVP_sha224(), args); | |
| } | |
| bool SHA256Sum(const std::vector<std::string> &args) { | |
| return DigestSum(EVP_sha256(), args); | |
| } | |
| bool SHA384Sum(const std::vector<std::string> &args) { | |
| return DigestSum(EVP_sha384(), args); | |
| } | |
| bool SHA512Sum(const std::vector<std::string> &args) { | |
| return DigestSum(EVP_sha512(), args); | |
| } | |
| bool SHA512256Sum(const std::vector<std::string> &args) { | |
| return DigestSum(EVP_sha512_256(), args); | |
| } | |
| BSSL_NAMESPACE_END |