aboutsummaryrefslogtreecommitdiffstats
path: root/hashpipe.c
diff options
context:
space:
mode:
Diffstat (limited to 'hashpipe.c')
-rw-r--r--hashpipe.c140
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;
+}