diff options
Diffstat (limited to 'hashpipe.c')
-rw-r--r-- | hashpipe.c | 140 |
1 files changed, 140 insertions, 0 deletions
diff --git a/hashpipe.c b/hashpipe.c new file mode 100644 index 0000000..d9b29ad --- /dev/null +++ b/hashpipe.c @@ -0,0 +1,140 @@ +/* SPDX-License-Identifier: GPL-2.0 + * + * Copyright (C) 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved. + */ + +#define _GNU_SOURCE + +#include <stdio.h> +#include <string.h> +#include <stdbool.h> +#include <unistd.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/sendfile.h> +#include <fcntl.h> +#include <openssl/evp.h> + +/* There's a function in OpenSSL for this too, but it's horrible to use. */ +static bool hex2bin(unsigned char *bin, const char *hex, size_t hexlen) +{ + unsigned char c, c_acc = 0, c_alpha0, c_alpha, c_num0, c_num, c_val; + volatile unsigned char ret = 0; + + if (hexlen % 2) + return false; + + for (unsigned int i = 0; i < hexlen; i += 2) { + c = (unsigned char)hex[i]; + c_num = c ^ 48U; + c_num0 = (c_num - 10U) >> 8; + c_alpha = (c & ~32U) - 55U; + c_alpha0 = ((c_alpha - 10U) ^ (c_alpha - 16U)) >> 8; + ret |= ((c_num0 | c_alpha0) - 1) >> 8; + c_val = (c_num0 & c_num) | (c_alpha0 & c_alpha); + c_acc = c_val * 16U; + + c = (unsigned char)hex[i + 1]; + c_num = c ^ 48U; + c_num0 = (c_num - 10U) >> 8; + c_alpha = (c & ~32U) - 55U; + c_alpha0 = ((c_alpha - 10U) ^ (c_alpha - 16U)) >> 8; + ret |= ((c_num0 | c_alpha0) - 1) >> 8; + c_val = (c_num0 & c_num) | (c_alpha0 & c_alpha); + bin[i / 2] = c_acc | c_val; + } + + return 1 & ((ret - 1) >> 8); + +} + +int main(int argc, char *argv[]) +{ + int fd; + off_t pos; + ssize_t len; + size_t hexlen, hashlen, filesize = 0; + const char *hex, *algo, *home; + unsigned char hash[1024], computed_hash[1024], buffer[1024 * 128]; + const EVP_MD *md; + EVP_MD_CTX *mdctx; + + if (argc != 3) { + fprintf(stderr, "Usage: %s ALGORITHM HASH\n", argv[0]); + return 1; + } + + home = getenv("HOME"); + fd = open(home ? home : "/", O_TMPFILE | O_EXCL | O_RDWR, S_IRUSR | S_IWUSR); + if (fd < 0) { + perror("Error: unable to create temporary inode"); + return 1; + } + + algo = argv[1]; + hex = argv[2]; + hexlen = strlen(hex); + + OpenSSL_add_all_digests(); + md = EVP_get_digestbyname(algo); + if (!md) { + fprintf(stderr, "Error: unknown hashing algorithm\n"); + return 1; + } + hashlen = EVP_MD_size(md); + if (hashlen > sizeof(hash) || hexlen % 2 || hexlen / 2 != hashlen) { + fprintf(stderr, "Error: invalid hash length\n"); + return 1; + } + if (!hex2bin(hash, hex, hexlen)) { + fprintf(stderr, "Error: invalid hash input\n"); + return 1; + } + + mdctx = EVP_MD_CTX_create(); + if (!mdctx || !EVP_DigestInit_ex(mdctx, md, NULL)) { + fprintf(stderr, "Error: unable to create OpenSSL context\n"); + return 1; + } + while ((len = read(0, buffer, sizeof(buffer))) > 0) { + if (write(fd, buffer, len) != len) { + perror("Error: unable to write to temporary inode"); + return 1; + } + if (!EVP_DigestUpdate(mdctx, buffer, len)) { + fprintf(stderr, "Error: unable to update OpenSSL hash function\n"); + return 1; + } + filesize += len; + } + if (!EVP_DigestFinal_ex(mdctx, computed_hash, NULL)) { + fprintf(stderr, "Error: unable to finalize OpenSSL hash function\n"); + return 1; + } + if (CRYPTO_memcmp(computed_hash, hash, hashlen)) { + fprintf(stderr, "Error: input data does not hash to supplied hash\n"); + return 2; + } + + if (lseek(fd, 0, SEEK_SET) < 0) { + perror("Error: unable to seek to beginning of temporary file\n"); + return 1; + } + + for (pos = 0; filesize && (len = sendfile(1, fd, &pos, filesize)) > 0; filesize -= len); + if (len < 0) { + if (errno != EINVAL || pos != 0) { + perror("Error: unable sendfile data to stdout"); + return 1; + } + + while ((len = read(fd, buffer, sizeof(buffer))) > 0) { + if (write(1, buffer, len) != len) { + perror("Error: unable to write data to stdout"); + return 1; + } + } + } + return 0; +} |