commit 4038e852c1e13726ddb3e3e7394e237188b0bfe6
parent 9ff6f1b75fdd5361a97a48be1a31f701abb99e14
Author: Santtu Lakkala <santtu.lakkala@digital14.com>
Date: Tue, 12 Sep 2023 19:01:16 +0300
Semi-major rewrite
Split token handling and filew operations to separate compilation units.
Drop usage of byte order conversions, and possibly accessing invalid
alignment variables.
Diffstat:
M | Makefile | | | 42 | ++++++++++++++++++++++++++++++++++++------ |
A | db.c | | | 195 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | db.h | | | 31 | +++++++++++++++++++++++++++++++ |
M | main.c | | | 499 | ++++++++++++++++--------------------------------------------------------------- |
M | sha512.c | | | 12 | +++++++----- |
A | token.c | | | 142 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | token.h | | | 33 | +++++++++++++++++++++++++++++++++ |
M | util.c | | | 47 | +++++++++++++++++++++++++++++++++++++++-------- |
M | util.h | | | 69 | +++++++++++++++++++++++++++++++++++++++++++-------------------------- |
9 files changed, 622 insertions(+), 448 deletions(-)
diff --git a/Makefile b/Makefile
@@ -1,6 +1,6 @@
-CFLAGS = -g -W -Wall -std=c99
+CFLAGS = -W -Wall -std=c99 -g -Os
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
+SOURCES = sha1.c sha256.c sha512.c tiny-AES-c/aes.c main.c util.c db.c token.c
OBJS = ${SOURCES:.c=.o}
TEST_SOURCES = sha1.c sha256.c sha512.c util.c test.c
TEST_OBJS = ${TEST_SOURCES:.c=.o}
@@ -14,17 +14,17 @@ NAME=totp
all: ${NAME}
-totp: ${OBJS}
- ${CC} -o $@ ${OBJS} ${LDFLAGS}
+${NAME}: ${OBJS}
+ ${CC} ${CFLAGS} -o $@ ${OBJS} ${LDFLAGS}
test: ${TEST_OBJS};
- ${CC} -o $@ ${TEST_OBJS} ${LDFLAGS}
+ ${CC} ${CFLAGS} -o $@ ${TEST_OBJS} ${LDFLAGS}
.c.o:
${CC} -c $< -o $@ ${CFLAGS} ${AES_CFLAGS}
clean:
- rm ${OBJS}
+ rm -f ${OBJS}
install: all
mkdir -p "${DESTDIR}${BINDIR}"
@@ -33,3 +33,33 @@ install: all
mkdir -p "${DESTDIR}${MANDIR}"
cp -f "${NAME}.1" "${DESTDIR}${MANDIR}"
chmod 644 "${DESTDIR}${MANDIR}/${NAME}.1"
+
+db.o: db.h
+db.o: tiny-AES-c/aes.h
+db.o: token.h
+db.o: util.h
+main.o: arg.h
+main.o: db.h
+main.o: sha1.h
+main.o: sha256.h
+main.o: sha512.h
+main.o: tiny-AES-c/aes.h
+main.o: token.h
+main.o: util.h
+sha1.o: sha1.h
+sha1.o: util.h
+sha256.o: sha256.h
+sha256.o: util.h
+sha512.o: sha512.h
+sha512.o: util.h
+test.o: sha1.h
+test.o: sha256.h
+test.o: sha512.h
+test.o: util.h
+token.o: token.h
+token.o: util.h
+util.o: util.h
+db.h: token.h
+token.h: util.h
+
+${OBJS}: Makefile
diff --git a/db.c b/db.c
@@ -0,0 +1,195 @@
+#include <stdbool.h>
+#include <stdint.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include "db.h"
+#include "util.h"
+#include "token.h"
+#include "tiny-AES-c/aes.h"
+
+struct header {
+ uint8_t magic[4];
+ uint8_t version;
+};
+
+static 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)) {
+ errno = ENODATA;
+ 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)
+ return true;
+
+ errno = EPERM;
+ return false;
+}
+
+int db_open_read(const char *filename, struct AES_ctx *c) {
+ int fd = open(filename, O_RDONLY);
+
+ if (fd < 0)
+ return fd;
+
+ if (verify_db(fd, c))
+ return fd;
+
+ close(fd);
+ return -1;
+}
+
+static int write_header(int fd, struct AES_ctx *c)
+{
+ uint8_t wbuf[AES_BLOCKLEN];
+ int w;
+ uint8_t *wp = wbuf;
+ uint8_t * const wend = 1[&wbuf];
+ struct header *h;
+
+ randmem(wbuf, sizeof(wbuf));
+
+ 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 (wp < wend && (w = write(fd, wp, wend - wp)) > 0)
+ wp += w;
+
+ if (w < 0)
+ return -1;
+
+ if (wp < wend) {
+ errno = EIO;
+ return -1;
+ }
+
+ return 0;
+
+}
+
+int db_open_write(const char *filename, struct AES_ctx *c)
+{
+ int fd = open(filename, O_WRONLY | O_CREAT | O_EXCL, 0600);
+
+ if (fd < 0)
+ return fd;
+
+ if (write_header(fd, c)) {
+ int tmp_errno = errno;
+ (void)unlink(filename);
+ (void)close(fd);
+ errno = tmp_errno;
+
+ return -1;
+ }
+
+ return fd;
+}
+
+void db_foreach(int fd, struct AES_ctx *c,
+ void (*key_cb)(struct token *token,
+ void *data),
+ void *cb_data)
+{
+ uint8_t decbuf[512];
+ uint8_t rbuf[AES_BLOCKLEN];
+ uint8_t *dp = decbuf;
+ uint8_t * const dend = 1[&decbuf];
+ uint8_t *rp = rbuf;
+ uint8_t * const rend = 1[&rbuf];
+ int r;
+
+ while ((r = read(fd, rp, rend - rp)) > 0) {
+ struct totpkey *kh = (struct totpkey *)decbuf;
+ if ((rp += r) < rend)
+ continue;
+ AES_CBC_decrypt_buffer(c, rbuf, sizeof(rbuf));
+ if (dp + sizeof(rbuf) >= dend)
+ break;
+ dp = mempush(dp, rp = rbuf, sizeof(rbuf));
+
+ if (decbuf + sizeof(*kh) > dp ||
+ decbuf + sizeof(*kh) + kh->keylen + kh->desclen + kh->issuerlen > dp)
+ continue;
+
+ struct bytes key = { decbuf + sizeof(*kh), kh->keylen };
+ struct bytes desc = { key.data + key.len, kh->desclen };
+ struct bytes issuer = { desc.data + desc.len, kh->issuerlen };
+ key_cb(&(struct token){
+ key, desc, issuer,
+ kh->digest, readbeu64(kh->t0),
+ kh->digits, kh->period,
+ true
+ }, cb_data);
+
+ dp = decbuf;
+ }
+}
+
+int db_add_key(int fd, struct AES_ctx *c,
+ struct token *token)
+{
+ size_t ksz = sizeof(struct totpkey) + token->key.len + token->desc.len + token->issuer.len;
+ size_t i;
+ int w;
+
+ ksz = (ksz + AES_BLOCKLEN - 1) / AES_BLOCKLEN * AES_BLOCKLEN;
+
+ if (token->key.len > UINT8_MAX || token->desc.len > UINT8_MAX || token->issuer.len > UINT8_MAX) {
+ errno = EMSGSIZE;
+ return -1;
+ }
+
+ uint8_t buffer[1024];
+ uint8_t *wp = buffer;
+ uint64_t t0 = token->t0;
+ wp = mempush(wp, &(struct totpkey){
+ .t0 = { t0 >> 56, t0 >> 48, t0 >> 40, t0 >> 32, t0 >> 24, t0 >> 16, t0 >> 8, t0 },
+ .digest = token->digest,
+ .digits = token->digits,
+ .period = token->period,
+ .keylen = token->key.len,
+ .desclen = token->desc.len,
+ .issuerlen = token->issuer.len }, sizeof(struct totpkey));
+ wp = mempushb(wp, token->key);
+ wp = mempushb(wp, token->desc);
+ wp = mempushb(wp, token->issuer);
+ randmem(wp, buffer + ksz - wp);
+
+ 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;
+}
+
diff --git a/db.h b/db.h
@@ -0,0 +1,31 @@
+#ifndef DB_H
+#define DB_H
+
+#include <time.h>
+#include <limits.h>
+#include "token.h"
+
+struct AES_ctx;
+
+struct totpkey {
+ uint8_t t0[sizeof(uint64_t)];
+ uint8_t digest;
+ uint8_t digits;
+ uint8_t period;
+ uint8_t keylen;
+ uint8_t desclen;
+ uint8_t issuerlen;
+ uint8_t filler1;
+ uint8_t filler2;
+};
+
+int db_open_read(const char *filename, struct AES_ctx *c);
+int db_open_write(const char *filename, struct AES_ctx *c);
+void db_foreach(int fd, struct AES_ctx *c,
+ void (*key_cb)(struct token *token,
+ void *data),
+ void *cb_data);
+int db_add_key(int fd, struct AES_ctx *c,
+ struct token *token);
+
+#endif
diff --git a/main.c b/main.c
@@ -12,7 +12,6 @@
#include <time.h>
#include <unistd.h>
-#include <arpa/inet.h>
#include <sys/stat.h>
#include "sha1.h"
@@ -21,6 +20,8 @@
#include "tiny-AES-c/aes.h"
#include "arg.h"
#include "util.h"
+#include "db.h"
+#include "token.h"
#define SECRET_DB_PATH ".local/share/totp"
#define SECRET_DB_FILE "secrets.db"
@@ -28,22 +29,6 @@
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) = {
@@ -62,23 +47,11 @@ static size_t digest_sizes[] = {
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)
+static void print_base32(FILE *stream, struct bytes data)
{
const char *chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
+ uint8_t *buffer = data.data;
+ size_t len = data.len;
uint16_t v = 0;
size_t b = 0;
@@ -86,248 +59,64 @@ void print_base32(const uint8_t *buffer, size_t len)
v = v << 8 | *buffer++;
b += 8;
while (b >= 5) {
- printf("%c", chars[(v >> (b - 5)) & 31]);
+ fprintf(stream, "%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;
+ fprintf(stream, "%c", chars[(v << (5 - b)) & 31]);
}
-void write_header(int fd, struct AES_ctx *c)
+void print_key(struct token *token, void *data)
{
- 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;
+ FILE *stream = data;
(void)data;
- printf("%.*s by %.*s\n", (int)desclen, desc, (int)issuerlen, issuer);
+ fprintf(stream, "%.*s by %.*s\n", (int)token->desc.len, token->desc.data, (int)token->issuer.len, token->issuer.data);
}
-static void print_uriencode(const char *buf, size_t len, bool getarg)
+static void print_uriencode(FILE *stream, struct bytes data, bool getarg)
{
const char *escape = ":/@+% &?";
- while (len && *buf) {
- size_t pass = strncspn(buf, len, escape);
+ const char *buf = (const char *)data.data;
+ const char *end = buf + data.len;
+ while (buf < end && *buf) {
+ size_t pass = strncspn(buf, end - buf, escape);
printf("%.*s", (int)pass, buf);
buf += pass;
- len -= pass;
- while (len && *buf && strchr(escape, *buf)) {
+ while (buf < end && *buf && strchr(escape, *buf)) {
if (*buf == ' ' && getarg)
- printf("+");
+ fprintf(stream, "+");
else
- printf("%%%02" PRIx8, *(uint8_t *)buf);
+ fprintf(stream, "%%%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 print_keyuri(struct token *token,
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);
+ FILE *stream = data;
+
+ fputs("otpauth://totp/", stream);
+ if (token->issuer.len) {
+ print_uriencode(stream, token->issuer, false);
+ fputc(':', stream);
+ }
+ print_uriencode(stream, token->desc, false);
+ fputs("?secret=", stream);
+ print_base32(stream, token->key);
+ if (token->issuer.len) {
+ fputs("&issuer=", stream);
+ print_uriencode(stream, token->issuer, true);
}
- printf("&algorithm=%s&digits=%" PRIu8 "&period=%" PRIu8 "\n",
- digest_names[digest],
- digits,
- period);
+ fprintf(stream, "&algorithm=%s&digits=%" PRIu8 "&period=%" PRIu8 "\n",
+ digest_names[token->digest],
+ token->digits,
+ token->period);
}
struct generate_data {
@@ -335,35 +124,30 @@ struct generate_data {
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)
+void generate_token(struct token *token, void *data)
{
struct generate_data *d = data;
uint32_t modulo = 1;
uint8_t i;
- char descbuf[desclen + 1];
+ char descbuf[512];
+ char *dp = descbuf;
- (void)issuer;
- (void)issuerlen;
-
- memcpy(descbuf, desc, desclen);
- descbuf[desclen] = '\0';
+ if (token->issuer.len) {
+ dp = mempush(dp, token->issuer.data, token->issuer.len);
+ *dp++ = ':';
+ }
+ dp = mempush(dp, token->desc.data, token->desc.len);
+ *dp = '\0';
if (fnmatch(d->filter, descbuf, FNM_NOESCAPE))
return;
d->found = true;
- for (i = 0; i < digits; i++)
+ for (i = 0; i < token->digits; i++)
modulo *= 10;
- printf("%0*" PRIu32 "\n", (int)digits,
- totp(key, keylen, time(NULL), period, t0, digest_hmacs[digest], digest_sizes[digest]) % modulo);
+ printf("%0*" PRIu32 "\n", (int)token->digits,
+ totp(token->key.data, token->key.len, time(NULL), token->period, token->t0, digest_hmacs[token->digest], digest_sizes[token->digest]) % modulo);
}
struct write_filter_data {
@@ -372,31 +156,22 @@ struct write_filter_data {
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 write_filter_key(struct token *token,
void *data)
{
struct write_filter_data *d = data;
if (d->filter) {
- char descbuf[desclen + 1];
+ char descbuf[UINT8_MAX + 1];
- memcpy(descbuf, desc, desclen);
- descbuf[desclen] = '\0';
+ memcpy(descbuf, token->desc.data, token->desc.len);
+ descbuf[token->desc.len] = '\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);
+ db_add_key(d->fd, d->c, token);
}
enum cmd {
@@ -412,50 +187,18 @@ void usage()
{
fprintf(stderr,
"Usage: totp [OPTIONS]\n"
+ "-f <file>\tuse file as database\n"
"-k <pass>\tpassphrase for database encryption\n"
"-K <file>\tread encryption passphrase from file\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"
+ "-T <time>\toverride current time for token generation\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;
-}
-
static void setecho(bool echo)
{
struct termios tio;
@@ -609,82 +352,34 @@ int main(int argc, char *argv[])
switch (cmd) {
case CMD_LIST:
- free(newsecretfile);
- fd = open(secretfile, O_RDONLY);
- if (free_secretfile)
- free(secretfile);
+ fd = db_open_read(secretfile, &c);
if (fd < 0)
break;
- if (!verify_db(fd, &c))
- croak("Unable to open database, check passphrase");
- read_keys(fd, &c, print_key, NULL);
+ db_foreach(fd, &c, print_key, stdout);
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, "&");
- }
- }
+ struct token token = token_parse_uri(totpuri);
+ if (!token.valid)
+ croak("Invalid uri");
- fd = open(secretfile, O_RDONLY, 0600);
- if (fd >= 0)
- verify_db(fd, &c);
+ fd = db_open_read(secretfile, &c);
+ if (fd < 0 && errno != ENOENT)
+ croak("Opening existing db failed: %s", strerror(errno));
- wfd = open(newsecretfile,
- O_WRONLY | O_TRUNC | O_CREAT, 0600);
- write_header(wfd, &wc);
+ wfd = db_open_write(newsecretfile, &wc);
+ if (wfd < 0)
+ croak("Could not open temporary secret file: %s", strerror(errno));
if (fd >= 0) {
- read_keys(fd, &c, write_filter_key,
- &(struct write_filter_data){
- .fd = wfd, .c = &wc });
+ db_foreach(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);
+ db_add_key(wfd, &wc, &token);
close(wfd);
rename(newsecretfile, secretfile);
- free(newsecretfile);
- free(secretfile);
break;
}
@@ -693,51 +388,49 @@ int main(int argc, char *argv[])
keyquery = argv[0];
/* fall-through */
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);
- }
+ fd = db_open_read(secretfile, &c);
+ if (fd < 0)
+ croak("Could not open secret file: %s", strerror(errno));
+ gd.filter = keyquery;
+ db_foreach(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
- });
+ fd = db_open_read(secretfile, &c);
+ if (fd < 0) {
+ if (errno == ENOENT)
+ break;
+ croak("Could not open secret file: %s", strerror(errno));
+ }
+ wfd = db_open_write(newsecretfile, &wc);
+ if (wfd < 0)
+ croak("Could not open temporary secret file: %s", strerror(errno));
+ db_foreach(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);
+ fd = db_open_read(secretfile, &c);
if (fd < 0)
- break;
- verify_db(fd, &c);
- read_keys(fd, &c, print_keyuri, NULL);
+ croak("Could not open secret file: %s", strerror(errno));
+ db_foreach(fd, &c, print_keyuri, stdout);
close(fd);
break;
}
}
+ if (free_secretfile)
+ free(secretfile);
+ free(newsecretfile);
+
return 0;
}
diff --git a/sha512.c b/sha512.c
@@ -68,7 +68,7 @@ static inline void _sha512_update(uint64_t *h, const void *data)
0x113f9804bef90dae, 0x1b710b35131c471b, 0x28db77f523047d84, 0x32caab7b40c72493, 0x3c9ebe0a15c9bebc,
0x431d67c49c100d4c, 0x4cc5d4becb3e42b6, 0x597f299cfc657e2a, 0x5fcb6fab3ad6faec, 0x6c44198c4a475817
};
- const uint64_t *d = data;
+ const uint8_t *d = data;
uint64_t w[16];
size_t i;
@@ -76,8 +76,10 @@ static inline void _sha512_update(uint64_t *h, const void *data)
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 = 0; i < sizeof(w) / sizeof(*w); i++) {
+ w[i] = readbeu64(&d[i * sizeof(uint64_t)]);
+ rotmod8(wr, k[i], w[i]);
+ }
for (; i < sizeof(k) / sizeof(*k); i++)
rotmod8(wr, k[i], getnw(w, i));
@@ -121,11 +123,11 @@ void sha512_finish(struct sha512 *s)
} else {
memset(s->buffer + (s->len & 127) + 1, 0, 119 - (s->len & 127));
}
- ((uint64_t *)s->buffer)[15] = _htonll(s->len << 3);
+ writebeu64(&s->buffer[120], 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]);
+ writebeu64(&s->h[i], s->h[i]);
}
void sha384_init(struct sha384 *s)
diff --git a/token.c b/token.c
@@ -0,0 +1,142 @@
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <stdio.h>
+#include "token.h"
+#include "util.h"
+
+const char *digest_names[] = {
+ "SHA1",
+ "SHA224",
+ "SHA256",
+ "SHA384",
+ "SHA512",
+};
+
+static inline char dehex(const char *s)
+{
+ const uint8_t *u = (const uint8_t *)s;
+ static const uint8_t vals[256] = {
+ ['0'] = 1, ['1'] = 2, ['2'] = 3, ['3'] = 4, ['4'] = 5,
+ ['5'] = 6, ['6'] = 7, ['7'] = 8, ['8'] = 9, ['9'] = 10,
+ ['A'] = 11, ['B'] = 12, ['C'] = 13, ['D'] = 14, ['E'] = 15,
+ ['F'] = 16, ['a'] = 11, ['b'] = 12, ['c'] = 13, ['d'] = 14,
+ ['e'] = 15, ['f'] = 16,
+ };
+
+ if (!vals[u[0]] || !vals[u[1]])
+ return 0;
+
+ return (vals[u[0]] - 1) << 4 |
+ (vals[u[1]] - 1);
+}
+
+static struct bytes uridecode(struct bytes data, bool getarg)
+{
+ char *w = (char *)data.data;
+ char *end = w + data.len;
+ const char *r = w;
+
+ while (r < end) {
+ if (*r == '%') {
+ if (r + 2 >= end)
+ return (struct bytes){ 0 };
+ *w++ = dehex(++r);
+ if (!w[-1])
+ return (struct bytes){ 0 };
+ r += 2;
+ } else if (getarg && *r == '+') {
+ *w++ = ' ';
+ r++;
+ } else
+ *w++ = *r++;
+ }
+
+ return (struct bytes){ data.data, w - (char *)data.data };
+}
+
+
+static 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;
+}
+
+static uint8_t strtou8(const char *s, char **end) {
+ unsigned long v = strtoul(s, end, 10);
+ if (v > UINT8_MAX || v == 0) {
+ errno = ERANGE;
+ return 0;
+ }
+ return v;
+}
+
+struct token token_parse_uri(char *data) {
+ struct token rv = { .t0 = 0, .period = 60, .digits = 6, .digest = DIGEST_SHA1, .valid = false };
+ char *str;
+ char *i;
+ char *v;
+
+ if (!(str = if_prefix(data, "otpauth://totp/")))
+ return rv;
+
+ i = strchr(str, '?');
+ if (!i)
+ return rv;
+
+ rv.desc = uridecode((struct bytes){ (uint8_t *)str, i - str }, false);
+
+ if ((v = memchr(rv.desc.data, ':', rv.desc.len))) {
+ rv.issuer = (struct bytes){ rv.desc.data, v++ - (char *)rv.desc.data };
+ rv.desc = (struct bytes){ (void *)v, i - v };
+ }
+
+ while (*i++) {
+ if ((v = if_prefix(i, "secret="))) {
+ if (rv.key.len)
+ croak("Multiple secrets in URI");
+ i = v + strcspn(v, "&");
+ rv.key = debase32(v, i - v);
+ } else if ((v = if_prefix(i, "digits="))) {
+ if (!(rv.digits = strtou8(v, &i)))
+ return rv;
+ } else if ((v = if_prefix(i, "period="))) {
+ if (!(rv.period = strtou8(v, &i)))
+ return rv;
+ } else if ((v = if_prefix(i, "issuer="))) {
+ i = v + strcspn(v, "&");
+ struct bytes newiss = uridecode((struct bytes){ (void *)v, i - v }, true);
+ if (rv.issuer.len && (newiss.len != rv.issuer.len ||
+ memcmp(rv.issuer.data, newiss.data, newiss.len))) {
+ errno = EINVAL;
+ return rv;
+ }
+ rv.issuer = newiss;
+ } else if ((v = if_prefix(i, "algorithm="))) {
+ i = v + strcspn(v, "&");
+ rv.digest = get_digest(v, i - v);
+ } else {
+ i += strcspn(i, "&");
+ }
+
+ if (!i && *i != '&') {
+ errno = EINVAL;
+ return rv;
+ }
+ }
+
+ if (rv.key.len && rv.desc.len)
+ rv.valid = true;
+ else
+ errno = EINVAL;
+
+ return rv;
+}
diff --git a/token.h b/token.h
@@ -0,0 +1,33 @@
+#ifndef TOKEN_H
+#define TOKEN_H
+
+#include <stdlib.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <time.h>
+#include "util.h"
+
+enum digest {
+ DIGEST_SHA1 = 0,
+ DIGEST_SHA224,
+ DIGEST_SHA256,
+ DIGEST_SHA384,
+ DIGEST_SHA512,
+};
+
+extern const char *digest_names[];
+
+struct token {
+ struct bytes key;
+ struct bytes desc;
+ struct bytes issuer;
+ enum digest digest;
+ time_t t0;
+ uint8_t digits;
+ uint8_t period;
+ bool valid;
+};
+
+struct token token_parse_uri(char *data);
+
+#endif /* TOKEN_H */
diff --git a/util.c b/util.c
@@ -63,11 +63,15 @@ uint32_t hotp(const void *key, size_t keylen,
void *h), size_t hshsz)
{
uint8_t h[hshsz];
+ uint8_t *pos;
hmac_f(key, keylen, counter, counterlen, h);
+ pos = &h[h[hshsz - 1] & 0xf];
- return ntohl(*(uint32_t *)&((uint8_t *)h)[h[hshsz - 1] & 0xf]) &
- 0x7fffffff;
+ return (uint32_t)(pos[0] & INT8_MAX) << 24 |
+ (uint32_t)pos[1] << 16 |
+ (uint32_t)pos[2] << 8 |
+ pos[3];
}
uint32_t totp(const void *key, size_t keylen,
@@ -77,9 +81,10 @@ uint32_t totp(const void *key, size_t keylen,
const void *data, size_t datalen,
void *h), size_t hshsz)
{
- uint64_t tv = _htonll((t1 - t0) / period);
+ uint8_t tb[sizeof(uint64_t)];
+ writebeu64(tb, (t1 - t0) / period);
- return hotp(key, keylen, &tv, sizeof(tv), hmac_f, hshsz);
+ return hotp(key, keylen, &tb, sizeof(tb), hmac_f, hshsz);
}
size_t strncspn(const char *s, size_t l, const char *c)
@@ -128,21 +133,47 @@ void croak(const char *fmt, ...)
exit(1);
}
-size_t debase32(char *buffer, size_t len)
+struct bytes debase32(char *buffer, size_t len)
{
uint8_t *wp = (uint8_t *)buffer;
const uint8_t *rp = (const uint8_t *)buffer;
uint16_t v = 0;
size_t b = 0;
+ static const uint8_t val[256] = {
+ ['A'] = 1, ['B'] = 2, ['C'] = 3, ['D'] = 4, ['E'] = 5,
+ ['F'] = 6, ['G'] = 7, ['H'] = 8, ['I'] = 9, ['J'] = 10,
+ ['K'] = 11, ['L'] = 12, ['M'] = 13, ['N'] = 14, ['O'] = 15,
+ ['P'] = 16, ['Q'] = 17, ['R'] = 18, ['S'] = 19, ['T'] = 20,
+ ['U'] = 21, ['V'] = 22, ['W'] = 23, ['X'] = 24, ['Y'] = 25,
+ ['Z'] = 26, ['a'] = 1, ['b'] = 2, ['c'] = 3, ['d'] = 4,
+ ['e'] = 5, ['f'] = 6, ['g'] = 7, ['h'] = 8, ['i'] = 9,
+ ['j'] = 10, ['k'] = 11, ['l'] = 12, ['m'] = 13, ['n'] = 14,
+ ['o'] = 15, ['p'] = 16, ['q'] = 17, ['r'] = 18, ['s'] = 19,
+ ['t'] = 20, ['u'] = 21, ['v'] = 22, ['w'] = 23, ['x'] = 24,
+ ['y'] = 25, ['z'] = 26, ['2'] = 27, ['3'] = 28, ['4'] = 29,
+ ['5'] = 30, ['6'] = 31, ['7'] = 32
+ };
+
for (rp = (uint8_t *)buffer; (char *)rp - buffer < (ptrdiff_t)len && *rp && *rp != '='; rp++) {
- uint8_t c = *rp >= 'a' ? *rp - 'a' : *rp >= 'A' ? *rp - 'A' : *rp - '2' + 26;
- v = v << 5 | c;
+ uint8_t c = val[*rp];
+ if (!c)
+ return (struct bytes){ 0 };
+ v = v << 5 | (c - 1);
b += 5;
if (b >= 8) {
*wp++ = (v >> (b & 7)) & 255;
b -= 8;
}
}
- return (char *)wp - buffer;
+ return (struct bytes){ (void *)buffer, (char *)wp - buffer };
+}
+
+void randmem(void *mem, size_t n)
+{
+ uint8_t *wp = mem;
+ uint8_t *end = wp + n;
+
+ while (wp < end)
+ *wp++ = rand();
}
diff --git a/util.h b/util.h
@@ -4,15 +4,20 @@
#include <stdlib.h>
#include <stddef.h>
#include <stdint.h>
+#include <string.h>
#include <time.h>
-#include <arpa/inet.h>
+struct bytes {
+ uint8_t *data;
+ size_t len;
+};
typedef void (*digest_init)(void *c);
typedef void (*digest_update)(void *c, const void *data, size_t len);
typedef void (*digest_finish)(void *c);
void xormem(void *a, const void *b, size_t len);
+void randmem(void *a, size_t len);
void hmac(const void *key, size_t keylen,
const void *data, size_t datalen,
digest_init init,
@@ -40,31 +45,9 @@ uint32_t hotp(const void *key, size_t keylen,
size_t strncspn(const char *haystack, size_t haystacklen, const char *needles);
-static inline uint64_t _htonll(uint64_t v)
-{
- union {
- uint64_t v64;
- uint32_t v32[2];
- } rv;
- rv.v32[0] = htonl(v >> 32);
- rv.v32[1] = htonl(v & 0xffffffffU);
-
- return rv.v64;
-}
-
-static inline uint64_t _ntohll(uint64_t v)
-{
- union {
- uint64_t v64;
- uint32_t v32[2];
- } rv;
- rv.v64 = v;
-
- return (uint64_t)ntohl(rv.v32[0]) << 32 | ntohl(rv.v32[1]);
-}
-
-static inline void writebeu64(uint8_t *buffer, uint64_t v)
+static inline void *writebeu64(void *dest, uint64_t v)
{
+ uint8_t *buffer = dest;
*buffer++ = v >> 56;
*buffer++ = v >> 48;
*buffer++ = v >> 40;
@@ -73,9 +56,43 @@ static inline void writebeu64(uint8_t *buffer, uint64_t v)
*buffer++ = v >> 16;
*buffer++ = v >> 8;
*buffer++ = v;
+ return buffer;
+}
+
+static inline uint64_t readbeu64(const void *src)
+{
+ const uint8_t *buffer = src;
+ return (uint64_t)buffer[0] << 56 |
+ (uint64_t)buffer[1] << 48 |
+ (uint64_t)buffer[2] << 40 |
+ (uint64_t)buffer[3] << 32 |
+ (uint64_t)buffer[4] << 24 |
+ (uint64_t)buffer[5] << 16 |
+ (uint64_t)buffer[6] << 8 |
+ (uint64_t)buffer[7];
+}
+
+static inline void *mempush(void *dst, const void *src, size_t n)
+{
+ if (n)
+ memcpy(dst, src, n);
+ return (char *)dst + n;
+}
+
+static inline void *mempushb(void *dst, struct bytes data)
+{
+ return mempush(dst, data.data, data.len);
+}
+
+static inline char *if_prefix(const char *s, const char *prefix)
+{
+ while (*prefix)
+ if (*s++ != *prefix++)
+ return NULL;
+ return (char *)s;
}
void croak(const char *fmt, ...);
-size_t debase32(char *buffer, size_t len);
+struct bytes debase32(char *buffer, size_t len);
#endif