/**
* sha3sum – SHA-3 (Keccak) checksum calculator
*
* Copyright © 2013, 2014 Mattias Andrée (maandree@member.fsf.org)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "common.h"
#include <stdio.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <alloca.h>
#ifndef STDIN_PATH
# ifndef DEVDIR
# define DEVDIR "/dev"
# endif
# define STDIN_PATH DEVDIR "/stdin"
#endif
#define USER_ERROR(string) \
(fprintf(stderr, "%s: %s.\n", execname, string), 1)
#define ADD(arg, desc, ...) \
(arg ? args_add_option(args_new_argumented(NULL, arg, 0, __VA_ARGS__, NULL), desc) \
: args_add_option(args_new_argumentless(NULL, 0, __VA_ARGS__, NULL), desc))
/**
* Storage for binary hash
*/
static char* restrict hashsum = NULL;
/**
* Storage for hexadecimal hash
*/
static char* restrict hexsum = NULL;
/**
* Storage for binary version of expected checksum
*/
#define correct_binary hexsum
/**
* Whether a mismatch has been found or if a file was missing
*/
static int bad_found = 0;
/**
* `argv[0]` from `main`
*/
static char* execname;
/**
* Calculate a Keccak-family hashsum of a file,
* the content of the file is assumed non-sensitive
*
* @param fd The file descriptor of the file to hash
* @param state The hashing state, should not be initialised (memory leak otherwise)
* @param spec Specifications for the hashing algorithm
* @param suffix The data suffix, see `libkeccak_digest`
* @param hash Output array for the hashsum, have an allocation size of
* at least `(spec->output / 8) * sizeof(char)`, may be `NULL`
* @return Zero on success, -1 on error
*/
__attribute__((nonnull(2, 3)))
static int generalised_sum_fd_hex(int fd, libkeccak_state_t* restrict state,
const libkeccak_spec_t* restrict spec,
const char* restrict suffix, char* restrict hash)
{
ssize_t got;
struct stat attr;
size_t blksize = 4096, r_ptr = 0, w_ptr = 0;
char* restrict chunk;
char even = 1, buf = 0, c;
if (libkeccak_state_initialise(state, spec) < 0)
return -1;
if (fstat(fd, &attr) == 0)
if (attr.st_blksize > 0)
blksize = (size_t)(attr.st_blksize);
chunk = alloca(blksize);
for (;;)
{
got = read(fd, chunk, blksize);
if (got < 0) return -1;
if (got == 0) break;
while (r_ptr < (size_t)got)
{
if (c = chunk[r_ptr++], c <= ' ')
continue;
buf = (buf << 4) | ((c & 15) + (c > '9' ? 9 : 0));
if ((even ^= 1))
chunk[w_ptr++] = buf;
}
if (libkeccak_fast_update(state, chunk, w_ptr) < 0)
return -1;
}
return libkeccak_fast_digest(state, NULL, 0, 0, suffix, hash);
}
/**
* Convert `libkeccak_generalised_spec_t` to `libkeccak_spec_t` and check for errors
*
* @param gspec See `libkeccak_degeneralise_spec`
* @param spec See `libkeccak_degeneralise_spec`
* @return Zero on success, an appropriate exit value on error
*/
static int make_spec(libkeccak_generalised_spec_t* restrict gspec, libkeccak_spec_t* restrict spec)
{
int r;
if (r = libkeccak_degeneralise_spec(gspec, spec), r)
switch (r)
{
case LIBKECCAK_GENERALISED_SPEC_ERROR_STATE_NONPOSITIVE:
return USER_ERROR("the state size must be positive");
case LIBKECCAK_GENERALISED_SPEC_ERROR_STATE_TOO_LARGE:
return USER_ERROR("the state size is too large, may not exceed 1600");
case LIBKECCAK_GENERALISED_SPEC_ERROR_STATE_MOD_25:
return USER_ERROR("the state size must be a multiple of 25");
case LIBKECCAK_GENERALISED_SPEC_ERROR_WORD_NONPOSITIVE:
return USER_ERROR("the word size must be positive");
case LIBKECCAK_GENERALISED_SPEC_ERROR_WORD_TOO_LARGE:
return USER_ERROR("the word size is too large, may not exceed 64");
case LIBKECCAK_GENERALISED_SPEC_ERROR_STATE_WORD_INCOHERENCY:
return USER_ERROR("the state size must be exactly 25 times the word size");
case LIBKECCAK_GENERALISED_SPEC_ERROR_CAPACITY_NONPOSITIVE:
return USER_ERROR("the capacity must be positive");
case LIBKECCAK_GENERALISED_SPEC_ERROR_CAPACITY_MOD_8:
return USER_ERROR("the capacity must be a multiple of 8");
case LIBKECCAK_GENERALISED_SPEC_ERROR_BITRATE_NONPOSITIVE:
return USER_ERROR("the rate must be positive");
case LIBKECCAK_GENERALISED_SPEC_ERROR_BITRATE_MOD_8:
return USER_ERROR("the rate must be a multiple of 8");
case LIBKECCAK_GENERALISED_SPEC_ERROR_OUTPUT_NONPOSITIVE:
return USER_ERROR("the output size must be positive");
default:
return USER_ERROR("unknown error in algorithm parameters");
}
if (r = libkeccak_spec_check(spec), r)
switch (r)
{
case LIBKECCAK_SPEC_ERROR_BITRATE_NONPOSITIVE:
return USER_ERROR("the rate size must be positive");
case LIBKECCAK_SPEC_ERROR_BITRATE_MOD_8:
return USER_ERROR("the rate must be a multiple of 8");
case LIBKECCAK_SPEC_ERROR_CAPACITY_NONPOSITIVE:
return USER_ERROR("the capacity must be positive");
case LIBKECCAK_SPEC_ERROR_CAPACITY_MOD_8:
return USER_ERROR("the capacity must be a multiple of 8");
case LIBKECCAK_SPEC_ERROR_OUTPUT_NONPOSITIVE:
return USER_ERROR("the output size must be positive");
case LIBKECCAK_SPEC_ERROR_STATE_TOO_LARGE:
return USER_ERROR("the state size is too large, may not exceed 1600");
case LIBKECCAK_SPEC_ERROR_STATE_MOD_25:
return USER_ERROR("the state size must be a multiple of 25");
case LIBKECCAK_SPEC_ERROR_WORD_NON_2_POTENT:
return USER_ERROR("the word size must be a power of 2");
case LIBKECCAK_SPEC_ERROR_WORD_MOD_8:
return USER_ERROR("the word size must be a multiple of 8");
default:
return USER_ERROR("unknown error in algorithm parameters");
}
return 0;
}
/**
* Calculate the checksum of a file and store it in the global variable `hashsum`
*
* @param filename The file to hash
* @param spec Hashing parameters
* @param squeezes The number of squeezes to perform
* @param suffix The message suffix
* @param hex Whether to use hexadecimal input rather than binary
* @return Zero on success, an appropriate exit value on error
*/
static int hash(const char* restrict filename, const libkeccak_spec_t* restrict spec,
long squeezes, const char* restrict suffix, int hex)
{
libkeccak_state_t state;
size_t length;
int r, fd;
length = (size_t)((spec->output + 7) / 8);
if (hashsum == NULL)
if (hashsum = malloc(length * sizeof(char)), hashsum == NULL)
return perror(execname), 2;
if (hexsum == NULL)
if (hexsum = malloc((length * 2 + 1) * sizeof(char)), hexsum == NULL)
return perror(execname), 2;
if (fd = open(strcmp(filename, "-") ? filename : STDIN_PATH, O_RDONLY), fd < 0)
return r = (errno != ENOENT), perror(execname), r + 1;
if ((hex == 0 ? libkeccak_generalised_sum_fd : generalised_sum_fd_hex)
(fd, &state, spec, suffix, squeezes > 1 ? NULL : hashsum))
return perror(execname), close(fd), libkeccak_state_fast_destroy(&state), 2;
close(fd);
if (squeezes > 2) libkeccak_fast_squeeze(&state, squeezes - 2);
if (squeezes > 1) libkeccak_squeeze(&state, hashsum);
libkeccak_state_fast_destroy(&state);
return 0;
}
/**
* Check that file has a reported checksum, `bad_found` will be
* updated if the file is missing or incorrect
*
* @param spec Hashing parameters
* @param squeezes The number of squeezes to perform
* @param suffix The message suffix
* @param hex Whether to use hexadecimal input rather than binary
* @param filename The file to check
* @param correct_hash The expected checksum (any form of hexadecimal)
* @return Zero on success, an appropriate exit value on error
*/
static int check(const libkeccak_spec_t* restrict spec, long squeezes, const char* restrict suffix,
int hex, const char* restrict filename, const char* restrict correct_hash)
{
size_t length = (size_t)((spec->output + 7) / 8);
int r;
if (access(filename, F_OK))
{
bad_found = 1;
printf("%s: %s\n", filename, "Missing");
return 0;
}
if ((r = hash(filename, spec, squeezes, suffix, hex)))
return r;
libkeccak_unhex(correct_binary, correct_hash);
if ((r = memcmp(correct_binary, hashsum, length)))
bad_found = 1;
printf("%s: %s\n", filename, !r ? "OK" : "Fail");
return 0;
}
/**
* Check checksums from a file
*
* @param filename The file to hash
* @param spec Hashing parameters
* @param squeezes The number of squeezes to perform
* @param suffix The message suffix
* @param representation (unused)
* @param hex Whether to use hexadecimal input rather than binary
* @return Zero on success, an appropriate exit value on error
*/
static int check_checksums(const char* restrict filename, const libkeccak_spec_t* restrict spec,
long squeezes, const char* restrict suffix, int representation, int hex)
{
struct stat attr;
size_t blksize = 4096;
size_t size = 4096;
size_t ptr = 0;
ssize_t got;
char* buf = NULL;
char* new;
int fd = -1, rc = 2, stage, r;
size_t hash_start = 0, hash_end = 0;
size_t file_start = 0, file_end = 0;
char* hash;
char* file;
size_t hash_n;
if (fd = open(strcmp(filename, "-") ? filename : STDIN_PATH, O_RDONLY), fd < 0)
goto pfail;
if (fstat(fd, &attr) == 0)
{
if (attr.st_blksize > 0) blksize = (size_t)(attr.st_blksize);
if (attr.st_size > 0) size = (size_t)(attr.st_size);
}
size = size > blksize ? size : blksize;
if (buf = malloc(size), buf == NULL)
goto pfail;
for (;;)
{
if (ptr + blksize < size)
{
if (new = realloc(buf, size <<= 1), new == NULL)
goto pfail;
buf = new;
}
got = read(fd, buf + ptr, blksize);
if (got < 0) goto pfail;
else if (got == 0) break;
else ptr += (size_t)got;
}
if (ptr == size)
{
if (new = realloc(buf, size + 1), new == NULL)
goto pfail;
buf = new;
}
size = ptr;
close(fd), fd = -1;
buf[size++] = '\n';
for (ptr = 0, stage = 0; ptr < size; ptr++)
{
char c = buf[ptr];
if (stage == 0)
{
if (('0' <= c) && (c <= '9'));
else if (('a' <= c) && (c <= 'f'));
else if (('A' <= c) && (c <= 'F'));
else if ((c == ' ') || (c == '\t'))
hash_end = ptr, stage++;
else if ((c == '\n') || (c == '\f') || (c == '\r'))
hash_end = ptr, stage = 3;
else
{
rc = USER_ERROR("file is malformated");
goto fail;
}
}
else if (stage == 1)
{
if ((c == '\n') || (c == '\f') || (c == '\r'))
stage = 3;
else if ((c != ' ') && (c != '\t'))
file_start = ptr, stage++;
}
else if (stage == 2)
{
if ((c == '\n') || (c == '\f') || (c == '\r'))
file_end = ptr, stage++;
}
if (stage == 3)
{
if ((hash_start == hash_end) != (file_start == file_end))
{
rc = USER_ERROR("file is malformated");
goto fail;
}
if (hash_start != hash_end)
{
hash = buf + hash_start;
file = buf + file_start;
hash_n = hash_end - hash_start;
buf[hash_end] = '\0';
buf[file_end] = '\0';
if (hash_n % 2)
{
rc = USER_ERROR("file is malformated");
goto fail;
}
if (hash_n / 2 != (size_t)((spec->output + 7) / 8))
{
rc = USER_ERROR("algorithm parameter mismatch");
goto fail;
}
if ((r = check(spec, squeezes, suffix, hex, file, hash)))
{
rc = r;
goto fail;
}
}
stage = 0;
hash_start = hash_end = file_start = file_end = ptr + 1;
}
}
if (stage)
{
rc = USER_ERROR("file is malformated");
goto fail;
}
free(buf);
return 0;
pfail:
perror(execname);
fail:
free(buf);
if (fd >= 0)
close(fd);
return rc;
(void) representation;
}
/**
* Print the checksum of a file
*
* @param filename The file to hash
* @param spec Hashing parameters
* @param squeezes The number of squeezes to perform
* @param suffix The message suffix
* @param representation Either of `REPRESENTATION_BINARY`, `REPRESENTATION_UPPER_CASE`
* and `REPRESENTATION_LOWER_CASE`
* @param hex Whether to use hexadecimal input rather than binary
* @return Zero on success, an appropriate exit value on error
*/
static int print_checksum(const char* restrict filename, const libkeccak_spec_t* restrict spec,
long squeezes, const char* restrict suffix, int representation, int hex)
{
size_t length = (size_t)((spec->output + 7) / 8);
int r;
if ((r = hash(filename, spec, squeezes, suffix, hex)))
return r;
if (representation == REPRESENTATION_UPPER_CASE)
{
libkeccak_behex_upper(hexsum, hashsum, length);
printf("%s %s\n", hexsum, filename);
}
else if (representation == REPRESENTATION_LOWER_CASE)
{
libkeccak_behex_lower(hexsum, hashsum, length);
printf("%s %s\n", hexsum, filename);
}
else
{
size_t ptr = 0;
ssize_t wrote;
fflush(stdout);
while (length - ptr)
{
wrote = write(STDOUT_FILENO, hashsum, length - ptr);
if (wrote <= 0)
return perror(execname), 2;
ptr += (size_t)wrote;
}
}
return 0;
}
/**
* Cleanup allocations
*/
static inline void cleanup(void)
{
free(hashsum), hashsum = NULL;
free(hexsum), hexsum = NULL;
}
/**
* Parse the command line and calculate the hashes of the selected files
*
* @param argc The first argument from `main`
* @param argv The second argument from `main`
* @param gspec The default algorithm parameters
* @param suffix Message suffix
* @return An appropriate exit value
*/
int run(int argc, char* argv[], libkeccak_generalised_spec_t* restrict gspec, const char* restrict suffix)
{
int r, verbose = 0, presentation = REPRESENTATION_UPPER_CASE, hex = 0, check = 0;
long squeezes = 1;
size_t i;
libkeccak_spec_t spec;
int (*fun)(const char* restrict filename, const libkeccak_spec_t* restrict spec,
long squeezes, const char* restrict suffix, int representation, int hex);
execname = *argv;
ADD(NULL, "Display option summary", "-h", "--help");
ADD("RATE", "Select rate", "-R", "--bitrate", "--rate");
ADD("CAPACITY", "Select capacity", "-C", "--capacity");
ADD("SIZE", "Select output size", "-N", "-O", "--output-size", "--output");
ADD("SIZE", "Select state size", "-S", "-B", "--state-size", "--state");
ADD("SIZE", "Select word size", "-W", "--word-size", "--word");
ADD("COUNT", "Select squeeze count", "-Z", "--squeezes");
ADD(NULL, "Use upper-case output", "-u", "--upper", "--uppercase", "--upper-case");
ADD(NULL, "Use lower-case output", "-l", "--lower", "--lowercase", "--lower-case");
ADD(NULL, "Use binary output", "-b", "--binary");
ADD(NULL, "Use hexadecimal input", "-x", "--hex", "--hex-input");
ADD(NULL, "Check checksums", "-c", "--check");
ADD(NULL, "Be verbose", "-v", "--verbose");
/* --check has been added because the sha1sum, sha256sum &c have it,
* but I ignore the other crap, mostly because not all implemention
* have them and binary vs text mode is stupid. */
args_parse(argc, argv);
if (args_opts_used("-h")) return args_help(0), 0;
if (args_opts_used("-R")) gspec->bitrate = atol(args_opts_get("-R")[0]);
if (args_opts_used("-C")) gspec->capacity = atol(args_opts_get("-C")[0]);
if (args_opts_used("-N")) gspec->output = atol(args_opts_get("-N")[0]);
if (args_opts_used("-S")) gspec->state_size = atol(args_opts_get("-S")[0]);
if (args_opts_used("-W")) gspec->word_size = atol(args_opts_get("-W")[0]);
if (args_opts_used("-Z")) squeezes = atol(args_opts_get("-Z")[0]);
if (args_opts_used("-u")) presentation = REPRESENTATION_UPPER_CASE;
if (args_opts_used("-l")) presentation = REPRESENTATION_LOWER_CASE;
if (args_opts_used("-b")) presentation = REPRESENTATION_BINARY;
if (args_opts_used("-x")) hex = 1;
if (args_opts_used("-c")) check = 1;
if (args_opts_used("-v")) verbose = 1;
fun = check ? check_checksums : print_checksum;
if ((r = make_spec(gspec, &spec)))
goto done;
if (squeezes <= 0)
{
r = USER_ERROR("the squeeze count most be positive");
goto done;
}
if (verbose)
{
fprintf(stderr, "rate: %li\n", gspec->bitrate);
fprintf(stderr, "capacity: %li\n", gspec->capacity);
fprintf(stderr, "output size: %li\n", gspec->output);
fprintf(stderr, "state size: %li\n", gspec->state_size);
fprintf(stderr, "word size: %li\n", gspec->word_size);
fprintf(stderr, "squeezes: %li\n", squeezes);
fprintf(stderr, "suffix: %s\n", suffix ? suffix : "");
}
if (args_files_count == 0)
r = fun("-", &spec, squeezes, suffix, presentation, hex);
else
for (i = 0; i < (size_t)args_files_count; i++)
if ((r = fun(args_files[i], &spec, squeezes, suffix, presentation, hex)))
break;
done:
args_dispose();
cleanup();
return r ? r : bad_found;
}