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