You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

141 lines
3.5 KiB
C

/* 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;
}