/* * Copyright (c) 2017, 2020 Jonas 'Sortie' Termansen. * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * checksum.c * Compute and check cryptographic hashes. */ #include #include #include #include #include #include #include #include #include #include static const char hexchars[] = "0123456789abcdef"; static uint8_t buffer[65536]; #define DIGEST_MAX_LENGTH SHA512_DIGEST_LENGTH union ctx { SHA2_CTX sha2; }; struct hash { const char* name; size_t digest_size; void (*init)(union ctx* ctx); void (*update)(union ctx* ctx, const uint8_t* buffer, size_t size); void (*final)(uint8_t digest[], union ctx* ctx); }; #define WRAP(ctx_member, algorithm) \ static void Wrap##algorithm##Init(union ctx* ctx) \ { \ algorithm##Init(&ctx->ctx_member); \ } \ \ static void Wrap##algorithm##Update(union ctx* ctx, \ const uint8_t* buffer, \ size_t size) \ { \ algorithm##Update(&ctx->ctx_member, buffer, size); \ } \ \ static void Wrap##algorithm##Final(uint8_t digest[], union ctx* ctx) \ { \ algorithm##Final(digest, &ctx->ctx_member); \ } WRAP(sha2, SHA224) WRAP(sha2, SHA256) WRAP(sha2, SHA384) WRAP(sha2, SHA512_256) WRAP(sha2, SHA512) #define HASH(variable, name, algorithm) \ static struct hash variable = \ { \ name, \ algorithm##_DIGEST_LENGTH, \ Wrap##algorithm##Init, \ Wrap##algorithm##Update, \ Wrap##algorithm##Final, \ } HASH(sha224, "SHA224", SHA224); HASH(sha256, "SHA256", SHA256); HASH(sha384, "SHA384", SHA384); HASH(sha512_256, "SHA512/256", SHA512_256); HASH(sha512, "SHA512", SHA512); static struct hash* hashes[] = { &sha224, &sha256, &sha384, &sha512_256, &sha512, NULL, }; static struct hash* hash = NULL; static const char* algorithm = NULL; static const char* checklist = NULL; static bool check = false; static bool ignore_missing = false; static bool quiet = false; static bool silent = false; int debase(char c) { if ( '0' <= c && c <= '9' ) return c - '0'; if ( 'a' <= c && c <= 'f' ) return c - 'a' + 10; if ( 'A' <= c && c <= 'F' ) return c - 'A' + 10; return -1; } static void printhex(const uint8_t* buffer, size_t size) { for ( size_t i = 0; i < size; i++ ) { putchar(hexchars[buffer[i] >> 4]); putchar(hexchars[buffer[i] & 0xF]); } } static int digest_fd(uint8_t digest[DIGEST_MAX_LENGTH], int fd, const char* path) { union ctx ctx; hash->init(&ctx); ssize_t amount; while ( 0 < (amount = read(fd, buffer, sizeof(buffer))) ) hash->update(&ctx, buffer, amount); if ( amount < 0 ) { warn("%s", path); return 1; } hash->final(digest, &ctx); return 0; } static int digest_path(uint8_t digest[DIGEST_MAX_LENGTH], const char* path) { if ( !strcmp(path, "-") ) return digest_fd(digest, 0, "-"); int fd = open(path, O_RDONLY); if ( fd < 0 ) { if ( errno == ENOENT && ignore_missing ) return -1; warn("%s", path); return 1; } int result = digest_fd(digest, fd, path); close(fd); return result; } static int verify_path(uint8_t checksum[], const char* path) { uint8_t digest[DIGEST_MAX_LENGTH]; int status = digest_path(digest, path); if ( status == -1 ) return status; if ( status == 0 && timingsafe_memcmp(checksum, digest, hash->digest_size) != 0 ) status = 2; explicit_bzero(digest, sizeof(digest)); if ( !silent && (!quiet || status != 0) ) printf("%s: %s\n", path, status == 0 ? "OK" : "FAILED"); return status; } struct checklist { const char* file; uint8_t checksum[DIGEST_MAX_LENGTH]; bool initialized; }; static int compare_checklist_file(const void* a_ptr, const void* b_ptr) { struct checklist* a = *(struct checklist**) a_ptr; struct checklist* b = *(struct checklist**) b_ptr; return strcmp(a->file, b->file); } static int search_checklist_file(const void* file_ptr, const void* elem_ptr) { const char* file = (const char*) file_ptr; struct checklist* elem = *(struct checklist**) elem_ptr; return strcmp(file, elem->file); } static int checklist_fp(FILE* fp, const char* path, size_t files_count, const char* const* files) { struct checklist* checklist = NULL; struct checklist** checklist_sorted = NULL; if ( files ) { checklist = calloc(sizeof(struct checklist), files_count); checklist_sorted = calloc(sizeof(struct checklist*), files_count); if ( !checklist || !checklist_sorted ) err(1, "malloc"); for ( size_t i = 0; i < files_count; i++ ) { checklist[i].file = files[i]; checklist_sorted[i] = &checklist[i]; } qsort(checklist_sorted, files_count, sizeof(struct checklist*), compare_checklist_file); } uint8_t checksum[DIGEST_MAX_LENGTH]; bool any = false; char* line = NULL; size_t line_size = 0; ssize_t line_length; off_t line_number = 0; size_t read_failures = 0; size_t check_failures = 0; while ( 0 < (line_length = getline(&line, &line_size, fp)) ) { line_number++; if ( line[line_length - 1] != '\n' ) errx(1, "%s:%ji: Line was not terminated with a newline", path, (intmax_t) line_number); line[--line_length] = '\0'; if ( (size_t) line_length < 2 * hash->digest_size ) errx(1, "%s:%ji: Improperly formatted %s checksum line", path, (intmax_t) line_number, hash->name); for ( size_t i = 0; i < hash->digest_size; i++ ) { int higher = debase(line[i*2 + 0]); int lower = debase(line[i*2 + 1]); if ( higher == -1 || lower == -1 ) errx(1, "%s:%ji: Improperly formatted %s checksum line", path, (intmax_t) line_number, hash->name); checksum[i] = higher << 4 | lower; } if ( line[2 * hash->digest_size + 0] != ' ' || line[2 * hash->digest_size + 1] != ' ' || line[2 * hash->digest_size + 2] == '\0' ) errx(1, "%s:%ji: Improperly formatted %s checksum line", path, (intmax_t) line_number, hash->name); const char* file = line + 2 * hash->digest_size + 2; if ( !strcmp(path, "-") && !strcmp(file, "-") ) errx(1, "%s:%ji: Improperly formatted %s checksum line", path, (intmax_t) line_number, hash->name); if ( files ) { struct checklist** entry_ptr = bsearch(file, checklist_sorted, files_count, sizeof(struct checksum*), search_checklist_file); if ( entry_ptr ) { struct checklist* entry = *entry_ptr; if ( entry->initialized ) errx(1, "%s:%ji: Duplicate hash found for: %s", path, (intmax_t) line_number, file); memcpy(entry->checksum, checksum, DIGEST_MAX_LENGTH); entry->initialized = true; } } else { int status = verify_path(checksum, file); if ( status == 1 ) read_failures++; else if ( status == 2 ) check_failures++; } any = true; } free(line); if ( ferror(fp) ) err(1, "%s", path); if ( !any ) errx(1, "%s: No properly formatted %s checksum lines found", path, hash->name); for ( size_t i = 0; i < files_count; i++ ) { const char* file = files[i]; struct checklist* entry = &checklist[i]; if ( !entry->initialized ) errx(1, "%s: No hash found for: %s", path, file); int status = verify_path(entry->checksum, file); if ( status == 1 ) read_failures++; else if ( status == 2 ) check_failures++; } explicit_bzero(checksum, sizeof(checksum)); free(checklist); free(checklist_sorted); if ( read_failures ) warnx("WARNING: %zu listed %s could not be read", read_failures, read_failures == 1 ? "file" : "files"); if ( check_failures ) warnx("WARNING: %zu computed %s did NOT match", check_failures, check_failures == 1 ? "checksum" : "checksums"); return read_failures ? 1 : check_failures ? 2 : 0; } static int checklist_path(const char* path, size_t files_count, const char* const* files) { if ( !strcmp(path, "-") ) return checklist_fp(stdin, "-", files_count, files); FILE* fp = fopen(path, "r"); if ( !fp ) err(1, "%s", path); int result = checklist_fp(fp, path, files_count, files); fclose(fp); return result; } static void compact_arguments(int* argc, char*** argv) { for ( int i = 0; i < *argc; i++ ) { while ( i < *argc && !(*argv)[i] ) { for ( int n = i; n < *argc; n++ ) (*argv)[n] = (*argv)[n+1]; (*argc)--; } } } int main(int argc, char* argv[]) { char* argv0_last_slash = strrchr(argv[0], '/'); const char* argv0_basename = argv0_last_slash ? argv0_last_slash + 1 : argv[0]; for ( int i = 1; i < argc; i++ ) { const char* arg = argv[i]; if ( arg[0] != '-' || !arg[1] ) continue; argv[i] = NULL; if ( !strcmp(arg, "--") ) break; if ( arg[1] != '-' ) { char c; while ( (c = *++arg) ) switch ( c ) { case 'a': if ( !*(algorithm = arg + 1) ) { if ( i + 1 == argc ) errx(1, "option requires an argument -- 'a'"); algorithm = argv[i+1]; argv[++i] = NULL; } arg = "a"; break; case 'c': check = true; break; case 'C': if ( !*(checklist = arg + 1) ) { if ( i + 1 == argc ) errx(1, "option requires an argument -- 'C'"); checklist = argv[i+1]; argv[++i] = NULL; } arg = "C"; break; case 'i': ignore_missing = true; break; case 'q': quiet = true; break; case 's': silent = true; break; default: errx(1, "unknown option -- '%c'", c); } } else if ( !strcmp(arg, "--algorithm") ) { if ( i + 1 == argc ) errx(1, "option '--algorithm' requires an argument"); algorithm = argv[i+1]; argv[++i] = NULL; } else if ( !strncmp(arg, "--algorithm=", strlen("--algorithm=")) ) algorithm = arg + strlen("--algorithm="); else if ( !strcmp(arg, "--check") ) check = true; else if ( !strcmp(arg, "--checklist") ) { if ( i + 1 == argc ) errx(1, "option '--checklist' requires an argument"); checklist = argv[i+1]; argv[++i] = NULL; } else if ( !strncmp(arg, "--checklist=", strlen("--checklist=")) ) checklist = arg + strlen("--checklist="); else if ( !strcmp(arg, "--ignore-missing") ) ignore_missing = true; else if ( !strcmp(arg, "--quiet") ) quiet = true; else if ( !strcmp(arg, "--status") ) silent = true; else errx(1, "unknown option: %s", arg); } compact_arguments(&argc, &argv); if ( check && checklist ) errx(1, "The -c and -C options are mutually incompatible"); if ( !(check || checklist) && (ignore_missing || quiet || silent) ) errx(1, "The -iqs options require -c or -C"); if ( algorithm ) { for ( size_t i = 0; !hash && hashes[i]; i++ ) if ( !strcasecmp(hashes[i]->name, algorithm) ) hash = hashes[i]; if ( !hash ) errx(1, "No such hash algorithm: %s", algorithm); } else if ( !strcmp(argv0_basename, "sha224sum") ) hash = &sha224; else if ( !strcmp(argv0_basename, "sha256sum") ) hash = &sha256; else if ( !strcmp(argv0_basename, "sha384sum") ) hash = &sha384; else if ( !strcmp(argv0_basename, "sha512sum") ) hash = &sha512; else errx(1, "No hash algorithm was specified with -a"); bool read_failures = false; bool check_failures = false; if ( checklist ) { int result = checklist_path(checklist, argc - 1, (const char* const*) argv + 1); if ( result == 1 ) read_failures = true; else if ( result == 2 ) check_failures = true; } else if ( argc == 1 ) { if ( check ) { int result = checklist_fp(stdin, "-", 0, NULL); if ( result == 1 ) read_failures = true; else if ( result == 2 ) check_failures = true; } else { uint8_t digest[DIGEST_MAX_LENGTH]; int result = digest_fd(digest, 0, "-"); if ( result == 0 ) { printhex(digest, hash->digest_size); puts(" -"); explicit_bzero(digest, sizeof(digest)); } else if ( result == 1 ) read_failures = true; } } else for ( int i = 1; i < argc; i++ ) { if ( check ) { int result = checklist_path(argv[i], 0, NULL); if ( result == 1 ) read_failures = true; else if ( result == 2 ) check_failures = true; } else { uint8_t digest[DIGEST_MAX_LENGTH]; int result = digest_path(digest, argv[i]); if ( result == 0 ) { printhex(digest, hash->digest_size); printf(" %s\n", argv[i]); explicit_bzero(digest, sizeof(digest)); } else if ( result == 1 ) read_failures = true; } } if ( ferror(stdout) || fflush(stdout) == EOF ) return 1; return read_failures ? 1 : check_failures ? 2 : 0; }