initial commit
[authenticator.git] / authenticator.c
1 /* Copyright 2015 Jann Horn <jann@thejh.net>.
2  * See LICENSE for license information.
3  */
4
5 #define _GNU_SOURCE
6 #include <assert.h>
7 #include <stdlib.h>
8 #include <stdio.h>
9 #include <stdint.h>
10 #include <string.h> /* GNU basename */
11 #include <openssl/sha.h>
12 #include <openssl/rand.h>
13
14 #define eprintf(...) fprintf(stderr, __VA_ARGS__)
15
16 #define SALT_BITS 14
17 #define SALT_MASK 0x00003fffU /* lowest 14 bits */
18 #define HASH_MASK 0x00ffc000U /* next 10 bits */
19
20 void base64_encode(uint32_t n, char out[5]);
21 uint32_t base64_decode(char *in);
22
23 static void openssl_failure(void) {
24   eprintf("OpenSSL internal failure\n");
25   exit(3);
26 }
27
28 static uint32_t gen_authenticator(char *path, uint16_t salt) {
29   SHA256_CTX ctx;
30   if (!SHA256_Init(&ctx)) openssl_failure();
31
32   /* append the filename including the terminating nullbyte to prevent
33    * ambiguity regarding where the filename ends */
34   char *name = basename(path);
35   if (!SHA256_Update(&ctx, name, strlen(name)+1)) openssl_failure();
36
37   FILE *f = fopen(path, "r");
38   if (!f) {
39     perror("unable to open input file");
40     exit(1);
41   }
42   char buf[20000];
43   size_t len;
44   while ((len = fread(buf, 1, sizeof(buf), f)) > 0) {
45     if (!SHA256_Update(&ctx, buf, len)) openssl_failure();
46   }
47   if (!feof(f) || ferror(f)) {
48     eprintf("error occured while reading input file");
49     exit(1);
50   }
51   fclose(f);
52
53   if (!SHA256_Update(&ctx, &salt, sizeof(salt))) openssl_failure();
54
55   unsigned char digest[SHA256_DIGEST_LENGTH];
56   if (!SHA256_Final(digest, &ctx)) openssl_failure();
57
58   uint32_t authenticator = salt;
59   authenticator |= (digest[0] << SALT_BITS | digest[1] << (SALT_BITS+8)) & HASH_MASK;
60   return authenticator;
61 }
62
63 static void chop(char *str) {
64   if (*str == '\0') return;
65   char *p = str + strlen(str) - 1;
66   while (p >= str && (*p == '\r' || *p == '\n')) {
67     *p = '\0';
68     p--;
69   }
70 }
71
72 #define RED "\x1b[0;31m"
73 #define GREEN "\x1b[0;32m"
74 #define RESET "\x1b[0m"
75
76 int main(int argc, char **argv) {
77   if (argc != 3) {
78 usage:
79     eprintf("bad invocation. usage:\n"
80             "%s <gen | verify> <path>\n", argv[0]);
81     return 2;
82   }
83
84   /* 4 bytes base64, 1 byte null, rest trailing garbage from fgets */
85   char b64_authenticator[30];
86
87   if (strcmp(argv[1], "gen") == 0) {
88     uint16_t salt;
89     if (RAND_bytes((unsigned char*)&salt, sizeof(salt)) != 1) openssl_failure();
90     salt &= SALT_MASK;
91     uint32_t authenticator = gen_authenticator(argv[2], salt);
92     base64_encode(authenticator, b64_authenticator);
93     printf("Authenticator generated: \x1b[7m%s\x1b[0m\n", b64_authenticator);
94     printf(RED"AFTER"RESET" the recipient confirms that he has received the file,\n"
95            "tell him the authenticator code and roughly compare the filename.\n");
96   } else if (strcmp(argv[1], "verify") == 0) {
97     printf("Please enter the 4-character authenticator: ");
98     if (!fgets(b64_authenticator, sizeof(b64_authenticator), stdin)) {
99       eprintf("unable to read user input\n");
100       return 2;
101     }
102     chop(b64_authenticator);
103     uint32_t authenticator = base64_decode(b64_authenticator);
104     if (gen_authenticator(argv[2], authenticator & SALT_MASK) == authenticator) {
105       printf("verification "GREEN"SUCCESSFUL"RESET"\n"
106              "If the filename of the received file matches\n"
107              "the one you expected, everything is fine.\n");
108     } else {
109       printf("verification "RED"FAILED"RESET"\n"
110              "Something nasty might be going on!\n"
111              RED"DO NOT RETRANSMIT THE FILE OR LET THE SENDER REGENERATE THE AUTHENTICATOR!"RESET"\n"
112              "Because there is a small inherent chance of attacks succeeding against this scheme,\n"
113              "retrying might make an attack possible. Find out what happened and, if you think an\n"
114              "attack might have happened, switch to normal cryptographic checksums.\n");
115       return 1;
116     }
117   } else {
118     goto usage;
119   }
120
121   return 0;
122 }