totp

Simple cli tool for storing TOTP secrets and generating tokens
git clone https://git.inz.fi/totp/
Log | Files | Refs | Submodules

commit 97a78b10a3e71f69a7b3ef9ae99c9985d9f27c23
Author: Santtu Lakkala <inz@inz.fi>
Date:   Tue,  9 Mar 2021 09:52:15 +0200

Initial import

Diffstat:
A.gitmodules | 3+++
AMakefile | 20++++++++++++++++++++
Aarg.h | 50++++++++++++++++++++++++++++++++++++++++++++++++++
Amain.c | 694+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asha1.c | 126+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asha1.h | 23+++++++++++++++++++++++
Asha256.c | 170+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asha256.h | 39+++++++++++++++++++++++++++++++++++++++
Asha512.c | 183+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asha512.h | 40++++++++++++++++++++++++++++++++++++++++
Atiny-AES-c | 1+
11 files changed, 1349 insertions(+), 0 deletions(-)

diff --git a/.gitmodules b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "tiny-AES-c"] + path = tiny-AES-c + url = https://github.com/kokke/tiny-AES-c diff --git a/Makefile b/Makefile @@ -0,0 +1,20 @@ +CFLAGS := -g -W -Wall -std=c99 +AES_CFLAGS += -DECB=0 -DCBC=1 -DCTR=0 -DAES256=1 +SOURCES := sha1.c sha256.c sha512.c tiny-AES-c/aes.c main.c util.c +OBJS := $(patsubst %.c,%.o,$(SOURCES)) +TEST_SOURCES := sha1.c sha256.c sha512.c util.c test.c +TEST_OBJS := $(patsubst %.c,%.o,$(TEST_SOURCES)) + +all: totp + +totp: $(OBJS) + $(CC) -o $@ $(OBJS) $(LDFLAGS) + +test: $(TEST_OBJS); + $(CC) -o $@ $(TEST_OBJS) $(LDFLAGS) + +%.o: %.c + $(CC) -c $< -o $@ $(CFLAGS) $(AES_CFLAGS) + +clean: + rm $(OBJS) diff --git a/arg.h b/arg.h @@ -0,0 +1,50 @@ +/* + * Copy me if you can. + * by 20h + */ + +#ifndef ARG_H__ +#define ARG_H__ + +extern char *argv0; + +/* use main(int argc, char *argv[]) */ +#define ARGBEGIN for (argv0 = *argv, argv++, argc--;\ + argv[0] && argv[0][0] == '-'\ + && argv[0][1];\ + argc--, argv++) {\ + char argc_;\ + char **argv_;\ + int brk_;\ + if (argv[0][1] == '-' && argv[0][2] == '\0') {\ + argv++;\ + argc--;\ + break;\ + }\ + int i_;\ + for (i_ = 1, brk_ = 0, argv_ = argv;\ + argv[0][i_] && !brk_;\ + i_++) {\ + if (argv_ != argv)\ + break;\ + argc_ = argv[0][i_];\ + switch (argc_) + +#define ARGEND }\ + } + +#define ARGC() argc_ + +#define EARGF(x) ((argv[0][i_+1] == '\0' && argv[1] == NULL)?\ + ((x), abort(), (char *)0) :\ + (brk_ = 1, (argv[0][i_+1] != '\0')?\ + (&argv[0][i_+1]) :\ + (argc--, argv++, argv[0]))) + +#define ARGF() ((argv[0][i_+1] == '\0' && argv[1] == NULL)?\ + (char *)0 :\ + (brk_ = 1, (argv[0][i_+1] != '\0')?\ + (&argv[0][i_+1]) :\ + (argc--, argv++, argv[0]))) + +#endif diff --git a/main.c b/main.c @@ -0,0 +1,694 @@ +#include <stdbool.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <errno.h> +#include <fcntl.h> +#include <fnmatch.h> +#include <inttypes.h> +#include <time.h> +#include <unistd.h> + +#include <arpa/inet.h> +#include <sys/stat.h> + +#include "sha1.h" +#include "sha256.h" +#include "sha512.h" +#include "tiny-AES-c/aes.h" +#include "arg.h" +#include "util.h" + +#define SECRET_DB_PATH ".local/share/totp" +#define SECRET_DB_FILE "secrets.db" +#define SECRET_DB_NEW_SUFFIX ".new" + +char *argv0; + +enum digest { + DIGEST_SHA1 = 0, + DIGEST_SHA224, + DIGEST_SHA256, + DIGEST_SHA384, + DIGEST_SHA512, +}; + +static const char *digest_names[] = { + "SHA1", + "SHA224", + "SHA256", + "SHA384", + "SHA512", +}; + +static void (*digest_hmacs[])(const void *key, size_t keylen, + const void *data, size_t datalen, + void *h) = { + sha1_hmac, + sha224_hmac, + sha256_hmac, + sha384_hmac, + sha512_hmac, +}; + +static size_t digest_sizes[] = { + SHA1_HASHSIZE, + SHA224_HASHSIZE, + SHA256_HASHSIZE, + SHA384_HASHSIZE, + SHA512_HASHSIZE, +}; + +uint8_t get_digest(const char *s, size_t len) +{ + size_t i; + + for (i = 0; i < sizeof(digest_names) / sizeof(*digest_names); i++) + if (!strncmp(s, digest_names[i], len) && + !digest_names[i][len]) + return i; + + fprintf(stderr, "Unknown digest \"%.*s\", assuming %s\n", + (int)len, s, digest_names[DIGEST_SHA1]); + return DIGEST_SHA1; +} + +void print_base32(const uint8_t *buffer, size_t len) +{ + const char *chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; + uint16_t v = 0; + size_t b = 0; + + while (len--) { + v = v << 8 | *buffer++; + b += 8; + while (b >= 5) { + printf("%c", chars[(v >> (b - 5)) & 31]); + b -= 5; + } + } + if (b) + printf("%c", chars[(v << (5 - b)) & 31]); +} + +char *_if_prefix(char *s, const char *prefix, size_t prefixlen) +{ + if (strncmp(s, prefix, prefixlen)) + return NULL; + return s + prefixlen; +} + +#define if_prefix(s, p) _if_prefix(s, p, sizeof(p) - 1) + +struct header { + uint8_t magic[4]; + uint8_t version; +}; + +bool verify_db(int fd, struct AES_ctx *c) +{ + uint8_t rbuf[AES_BLOCKLEN]; + int r; + size_t rused = 0; + struct header *h; + + while ((r = read(fd, rbuf + rused, sizeof(rbuf) - rused)) > 0) + rused += r; + + if (rused < sizeof(rbuf)) + return false; + + AES_CBC_decrypt_buffer(c, rbuf, sizeof(rbuf)); + h = (struct header *)(rbuf + rbuf[0] % (sizeof(rbuf) - sizeof(*h) - 1) + 1); + + if (h->magic[0] != 'T' || + h->magic[1] != 'O' || + h->magic[2] != 'T' || + h->magic[3] != 'P' || + h->version != 1) + croak("Secret database decryption failed, check passphrase"); + + return true; +} + +void write_header(int fd, struct AES_ctx *c) +{ + uint8_t wbuf[AES_BLOCKLEN]; + int w; + size_t written = 0; + size_t i; + struct header *h; + + for (i = 0; i < sizeof(wbuf); i++) + wbuf[i] = rand(); + + h = (struct header *)(wbuf + wbuf[0] % (sizeof(wbuf) - sizeof(*h) - 1) + 1); + + h->magic[0] = 'T'; + h->magic[1] = 'O'; + h->magic[2] = 'T'; + h->magic[3] = 'P'; + h->version = 1; + + AES_CBC_encrypt_buffer(c, wbuf, sizeof(wbuf)); + + while (written < sizeof(wbuf) && (w = write(fd, wbuf + written, sizeof(wbuf) - written)) > 0) + written += w; +} + +struct totpkey { + uint64_t t0; + uint8_t digest; + uint8_t digits; + uint8_t period; + uint8_t keylen; + uint8_t desclen; + uint8_t issuerlen; + uint8_t filler1; + uint8_t filler2; +}; + +void read_keys(int fd, struct AES_ctx *c, + void (*key_cb)(uint8_t digest, + uint8_t digits, + uint8_t period, + time_t t0, + const uint8_t *key, size_t keylen, + const char *desc, size_t desclen, + const char *issuer, size_t issuerlen, + void *data), + void *cb_data) +{ + uint8_t decbuf[512]; + uint8_t rbuf[AES_BLOCKLEN]; + size_t dused = 0; + size_t rused = 0; + int r; + + while ((r = read(fd, rbuf + rused, sizeof(rbuf) - rused)) > 0) { + struct totpkey *kh = (struct totpkey *)decbuf; + if ((rused += r) < sizeof(rbuf)) + continue; + AES_CBC_decrypt_buffer(c, rbuf, sizeof(rbuf)); + if (dused + sizeof(rbuf) >= sizeof(decbuf)) + break; + memcpy(decbuf + dused, rbuf, sizeof(rbuf)); + rused = 0; + dused += sizeof(rbuf); + + if (dused < sizeof(*kh) + + kh->keylen + kh->desclen + kh->issuerlen) + continue; + + key_cb(kh->digest, + kh->digits, + kh->period, + _ntohll(kh->t0), + decbuf + sizeof(*kh), kh->keylen, + (const char *)(decbuf + sizeof(*kh) + kh->keylen), + kh->desclen, + (const char *)(decbuf + sizeof(*kh) + kh->keylen + kh->desclen), + kh->issuerlen, + cb_data); + dused = 0; + } +} + +int write_key(int fd, struct AES_ctx *c, + uint8_t digest, + uint8_t digits, + uint8_t period, + time_t t0, + const uint8_t *key, size_t keylen, + const char *desc, size_t desclen, + const char *issuer, size_t issuerlen) +{ + size_t ksz = sizeof(struct totpkey) + keylen + desclen + issuerlen; + size_t i; + int w; + + ksz += AES_BLOCKLEN - 1 - ((ksz - 1) % AES_BLOCKLEN); + + if (keylen > UINT8_MAX || desclen > UINT8_MAX) + return -EINVAL; + + uint8_t buffer[ksz]; + memcpy(buffer, &(struct totpkey){ + .t0 = _htonll(t0), + .digest = digest, + .digits = digits, + .period = period, + .keylen = keylen, + .desclen = desclen, + .issuerlen = issuerlen }, sizeof(struct totpkey)); + memcpy(buffer + sizeof(struct totpkey), key, keylen); + memcpy(buffer + sizeof(struct totpkey) + keylen, desc, desclen); + memcpy(buffer + sizeof(struct totpkey) + keylen + desclen, issuer, issuerlen); + memset(buffer + sizeof(struct totpkey) + keylen + desclen + issuerlen, 0, + ksz - sizeof(struct totpkey) - keylen - desclen - issuerlen); + + for (i = 0; i < ksz; i += AES_BLOCKLEN) + AES_CBC_encrypt_buffer(c, buffer + i, AES_BLOCKLEN); + i = 0; + + while ((w = write(fd, buffer + i, ksz - i)) > 0) + i += w; + + if (w < 0) + return -errno; + return i != ksz; +} + +void print_key(uint8_t digest, + uint8_t digits, + uint8_t period, + time_t t0, + const uint8_t *key, size_t keylen, + const char *desc, size_t desclen, + const char *issuer, size_t issuerlen, + void *data) +{ + (void)digest; + (void)digits; + (void)period; + (void)key; + (void)keylen; + (void)issuer; + (void)issuerlen; + (void)t0; + + (void)data; + + printf("%.*s by %.*s\n", (int)desclen, desc, (int)issuerlen, issuer); +} + +static void print_uriencode(const char *buf, size_t len, bool getarg) +{ + const char *escape = ":/@+% &?"; + while (len && *buf) { + size_t pass = strncspn(buf, len, escape); + printf("%.*s", (int)pass, buf); + buf += pass; + len -= pass; + + while (len && *buf && strchr(escape, *buf)) { + if (*buf == ' ' && getarg) + printf("+"); + else + printf("%%%02" PRIx8, *(uint8_t *)buf); + buf++; + len--; + } + } +} + +void print_keyuri(uint8_t digest, + uint8_t digits, + uint8_t period, + time_t t0, + const uint8_t *key, size_t keylen, + const char *desc, size_t desclen, + const char *issuer, size_t issuerlen, + void *data) +{ + (void)t0; + (void)data; + printf("otpauth://totp/"); + print_uriencode(desc, desclen, false); + printf("?secret="); + print_base32(key, keylen); + if (issuerlen) { + printf("&issuer="); + print_uriencode(issuer, issuerlen, true); + } + printf("&algorithm=%s&digits=%" PRIu8 "&period=%" PRIu8 "\n", + digest_names[digest], + digits, + period); +} + +struct generate_data { + const char *filter; + bool found; +}; + +void generate_token(uint8_t digest, + uint8_t digits, + uint8_t period, + time_t t0, + const uint8_t *key, size_t keylen, + const char *desc, size_t desclen, + const char *issuer, size_t issuerlen, + void *data) +{ + struct generate_data *d = data; + uint32_t modulo = 1; + uint8_t i; + char descbuf[desclen + 1]; + + (void)issuer; + (void)issuerlen; + + memcpy(descbuf, desc, desclen); + descbuf[desclen] = '\0'; + + if (fnmatch(d->filter, descbuf, FNM_NOESCAPE)) + return; + + d->found = true; + for (i = 0; i < digits; i++) + modulo *= 10; + + printf("%0*" PRIu32 "\n", (int)digits, + totp(key, keylen, time(NULL), period, t0, digest_hmacs[digest], digest_sizes[digest]) % modulo); +} + +struct write_filter_data { + int fd; + const char *filter; + struct AES_ctx *c; +}; + +void write_filter_key(uint8_t digest, + uint8_t digits, + uint8_t period, + time_t t0, + const uint8_t *key, size_t keylen, + const char *desc, size_t desclen, + const char *issuer, size_t issuerlen, + void *data) +{ + struct write_filter_data *d = data; + + if (d->filter) { + char descbuf[desclen + 1]; + + memcpy(descbuf, desc, desclen); + descbuf[desclen] = '\0'; + + if (!fnmatch(d->filter, descbuf, FNM_NOESCAPE)) + return; + } + + write_key(d->fd, d->c, digest, digits, period, t0, + key, keylen, + desc, desclen, + issuer, issuerlen); +} + +enum cmd { + CMD_TOK, + CMD_LIST, + CMD_ADD, + CMD_DEL, + CMD_EXP +}; + +void usage() +{ + fprintf(stderr, + "Usage: totp [OPTIONS]\n" + "-l\tlist known secrets\n" + "-a <uri>\tadd uri to secrets\n" + "-d <filter>\tremove secrets matching filter\n" + "-t <filter>\tgenerate tokens for secrets matching filter\n" + "-e\texport secrets\n"); + exit(1); +} + +static inline char dehex(const char *s) +{ + if ((*s < '0' || + (*s > '9' && (*s & ~0x20) < 'A') || + (*s & ~0x20) > 'F') || + (s[1] < '0' || + (s[1] > '9' && (s[1] & ~0x20) < 'A') || + (s[1] & ~0x20) > 'F')) + return '?'; + return (*s < 'A' ? *s - '0' : (*s & ~0x20) - 'A' + 10) << 4 | + (s[1] < 'A' ? s[1] - '0' : (s[1] & ~0x20) - 'A' + 10); +} + +static size_t uridecode(char *buf, size_t len, bool getarg) +{ + char *w = buf; + const char *r = buf; + + while (r - buf < (ptrdiff_t)len) { + if (*r == '%') { + if (r - buf + 2 >= (ptrdiff_t)len) + break; + *w++ = dehex(++r); + r += 2; + } else if (getarg && *r == '+') { + *w++ = ' '; + r++; + } else + *w++ = *r++; + } + + return w - buf; +} + +int main(int argc, char *argv[]) +{ + int fd; + int r; + struct sha1 d; + enum cmd cmd; + char *totpuri; + const char *key = NULL; + const char *keyfile = NULL; + const char *keyquery = NULL; + char *secretfile = NULL; + char *newsecretfile = NULL; + bool free_secretfile = true; + uint8_t keybuf[AES_KEYLEN + AES_BLOCKLEN]; + size_t keylen = 0; + struct generate_data gd = { NULL, false }; + char *t; + + ARGBEGIN { + case 'l': + cmd = CMD_LIST; + break; + case 'a': + cmd = CMD_ADD; + totpuri = EARGF(usage()); + break; + case 'd': + cmd = CMD_DEL; + keyquery = EARGF(usage()); + break; + case 't': + cmd = CMD_TOK; + keyquery = EARGF(usage()); + break; + case 'e': + cmd = CMD_EXP; + break; + case 'k': + key = EARGF(usage()); + break; + case 'K': + keyfile = EARGF(usage()); + break; + case 'f': + secretfile = EARGF(usage()); + free_secretfile = false; + break; + default: + usage(); + break; + + } ARGEND + + sha1_init(&d); + + if (key) { + sha1_update(&d, key, strlen(key)); + } else if (keyfile) { + fd = open(keyfile, O_RDONLY); + if (fd < 0) + exit(1); + while ((r = read(fd, d.buffer + (d.len & 63), + sizeof(d.buffer) - (d.len & 63))) > 0) { + d.len += r; + if (!(d.len & 63)) + sha1_update(&d, d.buffer, sizeof(d.buffer)); + } + close(fd); + } + + sha1_finish(&d); + + while (keylen + sizeof(d.h) < sizeof(keybuf)) { + memcpy(keybuf + keylen, d.h, sizeof(d.h)); + memcpy(d.buffer, d.h, sizeof(d.h)); + sha1_init(&d); + sha1_update(&d, d.buffer, sizeof(d.h)); + sha1_finish(&d); + keylen += sizeof(d.h); + } + memcpy(keybuf + keylen, d.h, sizeof(keybuf) - keylen); + + struct AES_ctx c; + AES_init_ctx_iv(&c, + (uint8_t *)keybuf, (uint8_t *)keybuf + AES_KEYLEN); + + struct AES_ctx wc; + AES_init_ctx_iv(&wc, + (uint8_t *)keybuf, (uint8_t *)keybuf + AES_KEYLEN); + + int wfd; + + srand(time(NULL)); + + if (!secretfile) { + const char *home = getenv("HOME"); + secretfile = malloc(strlen(home) + sizeof(SECRET_DB_PATH) + sizeof(SECRET_DB_FILE) + 1); + sprintf(secretfile, "%s/%s/%s", home, SECRET_DB_PATH, SECRET_DB_FILE); + } + + newsecretfile = malloc(strlen(secretfile) + sizeof(SECRET_DB_NEW_SUFFIX)); + sprintf(newsecretfile, "%s%s", secretfile, SECRET_DB_NEW_SUFFIX); + + for (t = strtok(secretfile + 1, "/"); (t = strtok(NULL, "/")); ) { + if (mkdir(secretfile, 0700) && errno != EEXIST) + croak("Could not create secret db dir: %s", strerror(errno)); + t[-1] = '/'; + } + + switch (cmd) { + case CMD_LIST: + free(newsecretfile); + fd = open(secretfile, O_RDONLY); + if (free_secretfile) + free(secretfile); + if (fd < 0) + break; + if (!verify_db(fd, &c)) + croak("Unable to open database, check passphrase"); + read_keys(fd, &c, print_key, NULL); + close(fd); + break; + + case CMD_ADD: { + size_t kl = 0; + size_t dl = 0; + char *i; + char *key; + char *desc; + uint8_t digest = DIGEST_SHA1; + uint8_t digits = 6; + uint8_t period = 30; + uint8_t issuerlen = 0; + time_t t0 = 0; + char *issuer; + + if (!(desc = if_prefix(totpuri, "otpauth://totp/"))) + usage(); + + i = strchr(desc, '?'); + if (!i) + usage(); + + dl = uridecode(desc, i - desc, false); + + while (*i++) { + char *v; + if ((v = if_prefix(i, "secret="))) { + i = v + strcspn(v, "&"); + kl = debase32(key = v, i - v); + } else if ((v = if_prefix(i, "digits="))) { + digits = strtoul(v, &i, 10); + } else if ((v = if_prefix(i, "period="))) { + period = strtoul(v, &i, 10); + } else if ((v = if_prefix(i, "issuer="))) { + i = v + strcspn(v, "&"); + issuerlen = uridecode(issuer = v, i - v, true); + } else if ((v = if_prefix(i, "algorithm="))) { + i = v + strcspn(v, "&"); + digest = get_digest(v, i - v); + } else { + i += strcspn(i, "&"); + } + } + + fd = open(secretfile, O_RDONLY, 0600); + if (fd >= 0) + verify_db(fd, &c); + + wfd = open(newsecretfile, + O_WRONLY | O_TRUNC | O_CREAT, 0600); + write_header(wfd, &wc); + if (fd >= 0) { + read_keys(fd, &c, write_filter_key, + &(struct write_filter_data){ + .fd = wfd, .c = &wc }); + close(fd); + } + write_key(wfd, &wc, + digest, digits, period, t0, + (uint8_t *)key, kl, desc, dl, + issuer, issuerlen); + close(wfd); + + rename(newsecretfile, secretfile); + free(newsecretfile); + free(secretfile); + + break; + } + + case CMD_TOK: + free(newsecretfile); + fd = open(secretfile, O_RDONLY); + free(secretfile); + if (fd >= 0) { + verify_db(fd, &c); + gd.filter = keyquery; + read_keys(fd, &c, generate_token, &gd); + close(fd); + } + if (!gd.found) + croak("No secrets matching filter found"); + break; + + case CMD_DEL: { + fd = open(secretfile, O_RDONLY); + if (fd < 0) + exit(1); + wfd = open(newsecretfile, + O_WRONLY | O_TRUNC | O_CREAT, 0600); + verify_db(fd, &c); + write_header(wfd, &wc); + read_keys(fd, &c, write_filter_key, + &(struct write_filter_data){ + .fd = wfd, .filter = keyquery, + .c = &wc + }); + close(wfd); + close(fd); + rename(newsecretfile, secretfile); + free(newsecretfile); + free(secretfile); + break; + + case CMD_EXP: + free(newsecretfile); + fd = open(secretfile, O_RDONLY); + free(secretfile); + if (fd < 0) + break; + verify_db(fd, &c); + read_keys(fd, &c, print_keyuri, NULL); + close(fd); + break; + } + } + + return 0; +} diff --git a/sha1.c b/sha1.c @@ -0,0 +1,126 @@ +#include <stdint.h> +#include <string.h> +#include <stdlib.h> + +#include <arpa/inet.h> + +#include "sha1.h" +#include "util.h" + +static inline uint32_t rotl32(uint32_t x, uint8_t n) +{ + return x << n | x >> (32 - n); +} + +static inline void add5(uint32_t *dest, const uint32_t *src) +{ + size_t i; + + for (i = 0; i < 5; i++) + dest[i] += src[i]; +} + +static inline void rotmod5(uint32_t *a, uint32_t f, uint32_t k, uint32_t w) +{ + uint32_t t = rotl32(a[0], 5) + f + a[4] + k + w; + memmove(a + 1, a, 4 * sizeof(*a)); + a[2] = rotl32(a[2], 30); + a[0] = t; +} + +static inline uint32_t getnw(uint32_t *w, size_t i) +{ + return w[i & 15] = rotl32(w[(i + 13) & 15] ^ w[(i + 8) & 15] ^ w[(i + 2) & 15] ^ w[i & 15], 1); +} + +void sha1_init(struct sha1 *s) +{ + memcpy(s->h, (uint32_t[]){ 0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0 }, 5 * sizeof(*s->h)); + s->len = 0; +} + +static inline void _sha1_update(uint32_t *h, const void *data) +{ + const uint32_t k[4] = { 0x5a827999, 0x6ed9eba1, 0x8f1bbcdc, 0xca62c1d6 }; + const uint32_t *d = data; + + uint32_t w[16]; + size_t i; + + uint32_t wr[5]; + memcpy(wr, h, sizeof(wr)); + + for (i = 0; i < 16; i++) + rotmod5(wr, (wr[1] & wr[2]) | (~wr[1] & wr[3]), k[0], w[i] = ntohl(d[i])); + for (; i < 20; i++) + rotmod5(wr, (wr[1] & wr[2]) | (~wr[1] & wr[3]), k[0], getnw(w, i)); + for (; i < 40; i++) + rotmod5(wr, wr[1] ^ wr[2] ^ wr[3], k[1], getnw(w, i)); + for (; i < 60; i++) + rotmod5(wr, (wr[1] & wr[2]) | (wr[1] & wr[3]) | (wr[2] & wr[3]), k[2], getnw(w, i)); + for (; i < 80; i++) + rotmod5(wr, wr[1] ^ wr[2] ^ wr[3], k[3], getnw(w, i)); + + add5(h, wr); +} + +void sha1_update(struct sha1 *s, const void *data, size_t len) +{ + if ((s->len & 63) + len >= 64) { + const char *d = data; + if (s->len & 63) { + memcpy((uint8_t *)s->buffer + (s->len & 63), d, 64 - (s->len & 63)); + _sha1_update(s->h, s->buffer); + d += 64 - (s->len & 63); + s->len += 64 - (s->len & 63); + len -= 64 - (s->len & 63); + } + while (len >= 64) { + _sha1_update(s->h, d); + d += 64; + s->len += 64; + len -= 64; + } + memmove(s->buffer, d, len); + } else { + memmove(s->buffer + (s->len & 63), data, len); + } + s->len += len; +} + +void sha1_finish(struct sha1 *s) +{ + size_t i; + + ((uint8_t *)s->buffer)[s->len & 63] = 0x80; + if ((s->len & 63) > 55) { + memset((uint8_t *)s->buffer + (s->len & 63) + 1, 0, 63 - (s->len & 63)); + _sha1_update(s->h, s->buffer); + memset(s->buffer, 0, (s->len & 63) + 1); + } else { + memset((uint8_t *)s->buffer + (s->len & 63) + 1, 0, 55 - (s->len & 63)); + } + s->buffer[14] = htonl(s->len >> 29); + s->buffer[15] = htonl(s->len << 3); + _sha1_update(s->h, s->buffer); + + for (i = 0; i < sizeof(s->h) / sizeof(*s->h); i++) + s->h[i] = htonl(s->h[i]); +} + +void sha1_hmac(const void *key, size_t keylen, + const void *data, size_t datalen, + void *h) +{ + hmac(key, keylen, + data, datalen, + (digest_init)sha1_init, + (digest_update)sha1_update, + (digest_finish)sha1_finish, + sizeof(struct sha1), + sizeof(((struct sha1 *)0)->buffer), + sizeof(((struct sha1 *)0)->h), + (ptrdiff_t)&((struct sha1 *)0)->buffer, + (ptrdiff_t)&((struct sha1 *)0)->h, + h); +} diff --git a/sha1.h b/sha1.h @@ -0,0 +1,23 @@ +#ifndef SHA1_H +#define SHA1_H + +#include <stdlib.h> +#include <stdint.h> + +#define SHA1_HASHSIZE 20 + +struct sha1 { + uint32_t buffer[16]; + uint32_t h[5]; + uint64_t len; +}; + +void sha1_init(struct sha1 *s); +void sha1_update(struct sha1 *s, const void *data, size_t len); +void sha1_finish(struct sha1 *s); + +void sha1_hmac(const void *key, size_t keylen, + const void *data, size_t datalen, + void *h); + +#endif diff --git a/sha256.c b/sha256.c @@ -0,0 +1,170 @@ +#include <stdint.h> +#include <string.h> +#include <stdlib.h> + +#include <arpa/inet.h> + +#include "sha256.h" +#include "util.h" + +static inline uint32_t rotr32(uint32_t x, uint8_t n) +{ + return x >> n | x << (32 - n); +} + +static inline void add8(uint32_t *dest, const uint32_t *src) +{ + size_t i; + + for (i = 0; i < 8; i++) + dest[i] += src[i]; +} + +static inline void rotmod8(uint32_t *a, uint32_t k, uint32_t w) +{ + uint32_t t1 = a[7] + (rotr32(a[4], 6) ^ rotr32(a[4], 11) ^ rotr32(a[4], 25)) + ((a[4] & a[5]) ^ (~a[4] & a[6])) + k + w; + uint32_t t2 = (rotr32(a[0], 2) ^ rotr32(a[0], 13) ^ rotr32(a[0], 22)) + ((a[0] & a[1]) ^ (a[0] & a[2]) ^ (a[1] & a[2])); + + memmove(a + 1, a, 7 * sizeof(*a)); + + a[4] += t1; + a[0] = t1 + t2; +} + +static inline uint32_t getnw(uint32_t *w, size_t i) +{ + return w[i & 15] += + (rotr32(w[(i + 1) & 15], 7) ^ rotr32(w[(i + 1) & 15], 18) ^ (w[(i + 1) & 15] >> 3)) + + w[(i + 9) & 15] + + (rotr32(w[(i + 14) & 15], 17) ^ rotr32(w[(i + 14) & 15], 19) ^ (w[(i + 14) & 15] >> 10)); +} + +void sha256_init(struct sha256 *s) +{ + memcpy(s->h, (uint32_t[]){ 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19 }, sizeof(s->h)); + s->len = 0; +} + +static inline void _sha256_update(uint32_t *h, const void *data) +{ + const uint32_t k[] = { + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, + 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, + 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, + 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, + 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, + 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 + }; + const uint32_t *d = data; + + uint32_t w[16]; + size_t i; + + uint32_t wr[8]; + memcpy(wr, h, sizeof(wr)); + + for (i = 0; i < 16; i++) + rotmod8(wr, k[i], w[i] = ntohl(d[i])); + + for (; i < 64; i++) + rotmod8(wr, k[i], getnw(w, i)); + + add8(h, wr); +} + +void sha256_update(struct sha256 *s, const void *data, size_t len) +{ + if ((s->len & 63) + len >= 64) { + const char *d = data; + if (s->len & 63) { + memcpy(s->buffer + (s->len & 63), d, 64 - (s->len & 63)); + _sha256_update(s->h, s->buffer); + d += 64 - (s->len & 63); + s->len += 64 - (s->len & 63); + len -= 64 - (s->len & 63); + } + while (len >= 64) { + _sha256_update(s->h, d); + d += 64; + s->len += 64; + len -= 64; + } + memmove(s->buffer, d, len); + } else { + memmove(s->buffer + (s->len & 63), data, len); + } + s->len += len; +} + +void sha256_finish(struct sha256 *s) +{ + size_t i; + + s->buffer[s->len & 63] = 0x80; + if ((s->len & 63) > 55) { + memset(s->buffer + (s->len & 63) + 1, 0, 63 - (s->len & 63)); + _sha256_update(s->h, s->buffer); + memset(s->buffer, 0, (s->len & 63) + 1); + } else { + memset(s->buffer + (s->len & 63) + 1, 0, 55 - (s->len & 63)); + } + ((uint32_t *)s->buffer)[14] = htonl(s->len >> 29); + ((uint32_t *)s->buffer)[15] = htonl(s->len << 3); + _sha256_update(s->h, s->buffer); + + for (i = 0; i < sizeof(s->h) / sizeof(*s->h); i++) + s->h[i] = htonl(s->h[i]); +} + +void sha224_init(struct sha224 *s) +{ + struct sha256 *s256 = (struct sha256 *)s; + memcpy(s256->h, (uint32_t[]){ 0xc1059ed8, 0x367cd507, 0x3070dd17, 0xf70e5939, 0xffc00b31, 0x68581511, 0x64f98fa7, 0xbefa4fa4 }, sizeof(s256->h)); + s->len = 0; +} + +void sha224_update(struct sha224 *s, const void *data, size_t len) +{ + sha256_update((struct sha256 *)s, data, len); +} + +void sha224_finish(struct sha224 *s) +{ + sha256_finish((struct sha256 *)s); +} + +void sha256_hmac(const void *key, size_t keylen, + const void *data, size_t datalen, + void *h) +{ + hmac(key, keylen, + data, datalen, + (digest_init)sha256_init, + (digest_update)sha256_update, + (digest_finish)sha256_finish, + sizeof(struct sha256), + sizeof(((struct sha256 *)0)->buffer), + sizeof(((struct sha256 *)0)->h), + (ptrdiff_t)&((struct sha256 *)0)->buffer, + (ptrdiff_t)&((struct sha256 *)0)->h, + h); +} + +void sha224_hmac(const void *key, size_t keylen, + const void *data, size_t datalen, + void *h) +{ + hmac(key, keylen, + data, datalen, + (digest_init)sha224_init, + (digest_update)sha224_update, + (digest_finish)sha224_finish, + sizeof(struct sha224), + sizeof(((struct sha224 *)0)->buffer), + sizeof(((struct sha224 *)0)->h), + (ptrdiff_t)&((struct sha224 *)0)->buffer, + (ptrdiff_t)&((struct sha224 *)0)->h, + h); +} diff --git a/sha256.h b/sha256.h @@ -0,0 +1,39 @@ +#ifndef SHA256_H +#define SHA256_H + +#include <stdlib.h> +#include <stdint.h> + +#define SHA256_HASHSIZE 32 +#define SHA224_HASHSIZE 28 + +struct sha256 { + uint8_t buffer[64]; + uint32_t h[8]; + uint64_t len; +}; + +struct sha224 { + uint8_t buffer[64]; + uint32_t h[7]; + uint32_t h7; + uint64_t len; +}; + +void sha256_init(struct sha256 *s); +void sha256_update(struct sha256 *s, const void *data, size_t len); +void sha256_finish(struct sha256 *s); + +void sha256_hmac(const void *key, size_t keylen, + const void *data, size_t datalen, + void *h); + +void sha224_init(struct sha224 *s); +void sha224_update(struct sha224 *s, const void *data, size_t len); +void sha224_finish(struct sha224 *s); + +void sha224_hmac(const void *key, size_t keylen, + const void *data, size_t datalen, + void *h); + +#endif diff --git a/sha512.c b/sha512.c @@ -0,0 +1,183 @@ +#include <stdint.h> +#include <string.h> +#include <stdlib.h> + +#include <arpa/inet.h> + +#include "sha512.h" +#include "util.h" + +static inline uint64_t rotr64(uint64_t x, uint8_t n) +{ + return x >> n | x << (64 - n); +} + +static inline void add8(uint64_t *dest, const uint64_t *src) +{ + size_t i; + + for (i = 0; i < 8; i++) + dest[i] += src[i]; +} + +static inline void rotmod8(uint64_t *a, uint64_t k, uint64_t w) +{ + uint64_t t1 = a[7] + (rotr64(a[4], 14) ^ rotr64(a[4], 18) ^ rotr64(a[4], 41)) + ((a[4] & a[5]) ^ (~a[4] & a[6])) + k + w; + uint64_t t2 = (rotr64(a[0], 28) ^ rotr64(a[0], 34) ^ rotr64(a[0], 39)) + ((a[0] & a[1]) ^ (a[0] & a[2]) ^ (a[1] & a[2])); + + memmove(a + 1, a, 7 * sizeof(*a)); + + a[4] += t1; + a[0] = t1 + t2; +} + +static inline uint64_t getnw(uint64_t *w, size_t i) +{ + return w[i & 15] += + (rotr64(w[(i + 1) & 15], 1) ^ rotr64(w[(i + 1) & 15], 8) ^ (w[(i + 1) & 15] >> 7)) + + w[(i + 9) & 15] + + (rotr64(w[(i + 14) & 15], 19) ^ rotr64(w[(i + 14) & 15], 61) ^ (w[(i + 14) & 15] >> 6)); +} + +void sha512_init(struct sha512 *s) +{ + memcpy(s->h, (uint64_t[]){ + 0x6a09e667f3bcc908, 0xbb67ae8584caa73b, 0x3c6ef372fe94f82b, 0xa54ff53a5f1d36f1, + 0x510e527fade682d1, 0x9b05688c2b3e6c1f, 0x1f83d9abfb41bd6b, 0x5be0cd19137e2179 + }, sizeof(s->h)); + s->len = 0; +} + +static inline void _sha512_update(uint64_t *h, const void *data) +{ + const uint64_t k[] = { + 0x428a2f98d728ae22, 0x7137449123ef65cd, 0xb5c0fbcfec4d3b2f, 0xe9b5dba58189dbbc, 0x3956c25bf348b538, + 0x59f111f1b605d019, 0x923f82a4af194f9b, 0xab1c5ed5da6d8118, 0xd807aa98a3030242, 0x12835b0145706fbe, + 0x243185be4ee4b28c, 0x550c7dc3d5ffb4e2, 0x72be5d74f27b896f, 0x80deb1fe3b1696b1, 0x9bdc06a725c71235, + 0xc19bf174cf692694, 0xe49b69c19ef14ad2, 0xefbe4786384f25e3, 0x0fc19dc68b8cd5b5, 0x240ca1cc77ac9c65, + 0x2de92c6f592b0275, 0x4a7484aa6ea6e483, 0x5cb0a9dcbd41fbd4, 0x76f988da831153b5, 0x983e5152ee66dfab, + 0xa831c66d2db43210, 0xb00327c898fb213f, 0xbf597fc7beef0ee4, 0xc6e00bf33da88fc2, 0xd5a79147930aa725, + 0x06ca6351e003826f, 0x142929670a0e6e70, 0x27b70a8546d22ffc, 0x2e1b21385c26c926, 0x4d2c6dfc5ac42aed, + 0x53380d139d95b3df, 0x650a73548baf63de, 0x766a0abb3c77b2a8, 0x81c2c92e47edaee6, 0x92722c851482353b, + 0xa2bfe8a14cf10364, 0xa81a664bbc423001, 0xc24b8b70d0f89791, 0xc76c51a30654be30, 0xd192e819d6ef5218, + 0xd69906245565a910, 0xf40e35855771202a, 0x106aa07032bbd1b8, 0x19a4c116b8d2d0c8, 0x1e376c085141ab53, + 0x2748774cdf8eeb99, 0x34b0bcb5e19b48a8, 0x391c0cb3c5c95a63, 0x4ed8aa4ae3418acb, 0x5b9cca4f7763e373, + 0x682e6ff3d6b2b8a3, 0x748f82ee5defb2fc, 0x78a5636f43172f60, 0x84c87814a1f0ab72, 0x8cc702081a6439ec, + 0x90befffa23631e28, 0xa4506cebde82bde9, 0xbef9a3f7b2c67915, 0xc67178f2e372532b, 0xca273eceea26619c, + 0xd186b8c721c0c207, 0xeada7dd6cde0eb1e, 0xf57d4f7fee6ed178, 0x06f067aa72176fba, 0x0a637dc5a2c898a6, + 0x113f9804bef90dae, 0x1b710b35131c471b, 0x28db77f523047d84, 0x32caab7b40c72493, 0x3c9ebe0a15c9bebc, + 0x431d67c49c100d4c, 0x4cc5d4becb3e42b6, 0x597f299cfc657e2a, 0x5fcb6fab3ad6faec, 0x6c44198c4a475817 + }; + const uint64_t *d = data; + + uint64_t w[16]; + size_t i; + + uint64_t wr[8]; + memcpy(wr, h, sizeof(wr)); + + for (i = 0; i < sizeof(w) / sizeof(*w); i++) + rotmod8(wr, k[i], w[i] = _ntohll(d[i])); + + for (; i < sizeof(k) / sizeof(*k); i++) + rotmod8(wr, k[i], getnw(w, i)); + + add8(h, wr); +} + +void sha512_update(struct sha512 *s, const void *data, size_t len) +{ + if ((s->len & 127) + len >= 128) { + const char *d = data; + if (s->len & 128) { + memcpy(s->buffer + (s->len & 127), d, 128 - (s->len & 127)); + _sha512_update(s->h, s->buffer); + d += 128 - (s->len & 127); + s->len += 128 - (s->len & 127); + len -= 128 - (s->len & 127); + } + while (len >= 128) { + _sha512_update(s->h, d); + d += 128; + s->len += 128; + len -= 128; + } + memmove(s->buffer, d, len); + } else { + memmove(s->buffer + (s->len & 127), data, len); + } + s->len += len; +} + +void sha512_finish(struct sha512 *s) +{ + size_t i; + + s->buffer[s->len & 127] = 0x80; + if ((s->len & 127) > 111) { + memset(s->buffer + (s->len & 127) + 1, 0, 127 - (s->len & 127)); + _sha512_update(s->h, s->buffer); + memset(s->buffer, 0, (s->len & 127) + 1); + } else { + memset(s->buffer + (s->len & 127) + 1, 0, 119 - (s->len & 127)); + } + ((uint64_t *)s->buffer)[15] = _htonll(s->len << 3); + _sha512_update(s->h, s->buffer); + + for (i = 0; i < sizeof(s->h) / sizeof(*s->h); i++) + s->h[i] = _htonll(s->h[i]); +} + +void sha384_init(struct sha384 *s) +{ + struct sha512 *s5 = (struct sha512 *)s; + memcpy(s5->h, (uint64_t[]){ + 0xcbbb9d5dc1059ed8, 0x629a292a367cd507, 0x9159015a3070dd17, 0x152fecd8f70e5939, + 0x67332667ffc00b31, 0x8eb44a8768581511, 0xdb0c2e0d64f98fa7, 0x47b5481dbefa4fa4 + }, sizeof(s5->h)); + s5->len = 0; +} + +void sha384_update(struct sha384 *s, const void *data, size_t len) +{ + sha512_update((struct sha512 *)s, data, len); +} + +void sha384_finish(struct sha384 *s) +{ + sha512_finish((struct sha512 *)s); +} + +void sha512_hmac(const void *key, size_t keylen, + const void *data, size_t datalen, + void *h) +{ + hmac(key, keylen, + data, datalen, + (digest_init)sha512_init, + (digest_update)sha512_update, + (digest_finish)sha512_finish, + sizeof(struct sha512), + sizeof(((struct sha512 *)0)->buffer), + sizeof(((struct sha512 *)0)->h), + (ptrdiff_t)&((struct sha512 *)0)->buffer, + (ptrdiff_t)&((struct sha512 *)0)->h, + h); +} + +void sha384_hmac(const void *key, size_t keylen, + const void *data, size_t datalen, + void *h) +{ + hmac(key, keylen, + data, datalen, + (digest_init)sha384_init, + (digest_update)sha384_update, + (digest_finish)sha384_finish, + sizeof(struct sha384), + sizeof(((struct sha384 *)0)->buffer), + sizeof(((struct sha384 *)0)->h), + (ptrdiff_t)&((struct sha384 *)0)->buffer, + (ptrdiff_t)&((struct sha384 *)0)->h, + h); +} diff --git a/sha512.h b/sha512.h @@ -0,0 +1,40 @@ +#ifndef SHA512_H +#define SHA512_H + +#include <stdlib.h> +#include <stdint.h> + +#define SHA512_HASHSIZE 64 +#define SHA384_HASHSIZE 48 + +struct sha512 { + uint8_t buffer[128]; + uint64_t h[8]; + uint64_t len; +}; + +struct sha384 { + uint8_t buffer[128]; + uint64_t h[6]; + uint64_t h6; + uint64_t h7; + uint64_t len; +}; + +void sha512_init(struct sha512 *s); +void sha512_update(struct sha512 *s, const void *data, size_t len); +void sha512_finish(struct sha512 *s); + +void sha512_hmac(const void *key, size_t keylen, + const void *data, size_t datalen, + void *h); + +void sha384_init(struct sha384 *s); +void sha384_update(struct sha384 *s, const void *data, size_t len); +void sha384_finish(struct sha384 *s); + +void sha384_hmac(const void *key, size_t keylen, + const void *data, size_t datalen, + void *h); + +#endif diff --git a/tiny-AES-c b/tiny-AES-c @@ -0,0 +1 @@ +Subproject commit 12e7744b4919e9d55de75b7ab566326a1c8e7a67