+/* Copyright 2015 Jann Horn <jann@thejh.net>.
+ * See LICENSE for license information.
+ */
+
+#define _GNU_SOURCE
+#include <assert.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <string.h> /* GNU basename */
+#include <openssl/sha.h>
+#include <openssl/rand.h>
+
+#define eprintf(...) fprintf(stderr, __VA_ARGS__)
+
+#define SALT_BITS 14
+#define SALT_MASK 0x00003fffU /* lowest 14 bits */
+#define HASH_MASK 0x00ffc000U /* next 10 bits */
+
+void base64_encode(uint32_t n, char out[5]);
+uint32_t base64_decode(char *in);
+
+static void openssl_failure(void) {
+ eprintf("OpenSSL internal failure\n");
+ exit(3);
+}
+
+static uint32_t gen_authenticator(char *path, uint16_t salt) {
+ SHA256_CTX ctx;
+ if (!SHA256_Init(&ctx)) openssl_failure();
+
+ /* append the filename including the terminating nullbyte to prevent
+ * ambiguity regarding where the filename ends */
+ char *name = basename(path);
+ if (!SHA256_Update(&ctx, name, strlen(name)+1)) openssl_failure();
+
+ FILE *f = fopen(path, "r");
+ if (!f) {
+ perror("unable to open input file");
+ exit(1);
+ }
+ char buf[20000];
+ size_t len;
+ while ((len = fread(buf, 1, sizeof(buf), f)) > 0) {
+ if (!SHA256_Update(&ctx, buf, len)) openssl_failure();
+ }
+ if (!feof(f) || ferror(f)) {
+ eprintf("error occured while reading input file");
+ exit(1);
+ }
+ fclose(f);
+
+ if (!SHA256_Update(&ctx, &salt, sizeof(salt))) openssl_failure();
+
+ unsigned char digest[SHA256_DIGEST_LENGTH];
+ if (!SHA256_Final(digest, &ctx)) openssl_failure();
+
+ uint32_t authenticator = salt;
+ authenticator |= (digest[0] << SALT_BITS | digest[1] << (SALT_BITS+8)) & HASH_MASK;
+ return authenticator;
+}
+
+static void chop(char *str) {
+ if (*str == '\0') return;
+ char *p = str + strlen(str) - 1;
+ while (p >= str && (*p == '\r' || *p == '\n')) {
+ *p = '\0';
+ p--;
+ }
+}
+
+#define RED "\x1b[0;31m"
+#define GREEN "\x1b[0;32m"
+#define RESET "\x1b[0m"
+
+int main(int argc, char **argv) {
+ if (argc != 3) {
+usage:
+ eprintf("bad invocation. usage:\n"
+ "%s <gen | verify> <path>\n", argv[0]);
+ return 2;
+ }
+
+ /* 4 bytes base64, 1 byte null, rest trailing garbage from fgets */
+ char b64_authenticator[30];
+
+ if (strcmp(argv[1], "gen") == 0) {
+ uint16_t salt;
+ if (RAND_bytes((unsigned char*)&salt, sizeof(salt)) != 1) openssl_failure();
+ salt &= SALT_MASK;
+ uint32_t authenticator = gen_authenticator(argv[2], salt);
+ base64_encode(authenticator, b64_authenticator);
+ printf("Authenticator generated: \x1b[7m%s\x1b[0m\n", b64_authenticator);
+ printf(RED"AFTER"RESET" the recipient confirms that he has received the file,\n"
+ "tell him the authenticator code and roughly compare the filename.\n");
+ } else if (strcmp(argv[1], "verify") == 0) {
+ printf("Please enter the 4-character authenticator: ");
+ if (!fgets(b64_authenticator, sizeof(b64_authenticator), stdin)) {
+ eprintf("unable to read user input\n");
+ return 2;
+ }
+ chop(b64_authenticator);
+ uint32_t authenticator = base64_decode(b64_authenticator);
+ if (gen_authenticator(argv[2], authenticator & SALT_MASK) == authenticator) {
+ printf("verification "GREEN"SUCCESSFUL"RESET"\n"
+ "If the filename of the received file matches\n"
+ "the one you expected, everything is fine.\n");
+ } else {
+ printf("verification "RED"FAILED"RESET"\n"
+ "Something nasty might be going on!\n"
+ RED"DO NOT RETRANSMIT THE FILE OR LET THE SENDER REGENERATE THE AUTHENTICATOR!"RESET"\n"
+ "Because there is a small inherent chance of attacks succeeding against this scheme,\n"
+ "retrying might make an attack possible. Find out what happened and, if you think an\n"
+ "attack might have happened, switch to normal cryptographic checksums.\n");
+ return 1;
+ }
+ } else {
+ goto usage;
+ }
+
+ return 0;
+}
\ No newline at end of file