totp

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

commit e2d20608af48ffb09e571406c0fa1c03fd0bbb61
parent 709074f8d5efb2afcc13f4ca98712905277682d2
Author: Santtu Lakkala <santtu.lakkala@digital14.com>
Date:   Mon, 18 Sep 2023 16:52:00 +0300

Add salt to database

Use first AES block of file as salt. Keep support for legacy format for
reading.

Refactoring of SHA implementations, add simple file format tests for legacy
and current formats. Improve C99 compliance.

Diffstat:
MMakefile | 32+++++++++++++++++++++++---------
Malgotest.c | 24++++++++++++------------
Mdb.c | 73++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
Mdb.h | 4++--
Mmain.c | 27++++++++++-----------------
Msha1.c | 52++++++++++++++++++++++++++++++----------------------
Msha1.h | 2+-
Msha256.c | 52++++++++++++++++++++++++++++++----------------------
Msha512.c | 47++++++++++++++++++++++++++++-------------------
Dtest.sh | 67-------------------------------------------------------------------
Atests/test.db | 0
Atests/test.legacy.db | 2++
Atests/test.pw | 1+
Atests/test.sh | 91+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mutil.h | 10++++++++++
15 files changed, 308 insertions(+), 176 deletions(-)

diff --git a/Makefile b/Makefile @@ -1,6 +1,8 @@ +.POSIX: CFLAGS = -W -Wall -Wextra -pedantic -std=c99 -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 db.c token.c +HEADERS = sha1.h sha256.h sha512.h tiny-AES-c/aes.h util.h db.h token.h OBJS = ${SOURCES:.c=.o} TEST_SOURCES = sha1.c sha256.c sha512.c util.c algotest.c TEST_OBJS = ${TEST_SOURCES:.c=.o} @@ -14,6 +16,10 @@ NAME=totp all: ${NAME} +debug: clean + ${MAKE} CFLAGS="-W -Wall -Wextra -pedantic -std=c99 -fsanitize=undefined,address,pointer-compare -O2 -g" NAME=${NAME}-dbg ${NAME}-dbg test + ${MAKE} clean + ${NAME}: ${OBJS} ${CC} ${CFLAGS} -o $@ ${OBJS} ${LDFLAGS} @@ -22,13 +28,13 @@ algotest: ${TEST_OBJS}; test: algotest ${NAME} ./algotest - ./test.sh ./${NAME} + cd tests; ./test.sh ../${NAME} .c.o: ${CC} -c $< -o $@ ${CFLAGS} ${AES_CFLAGS} clean: - rm -f ${OBJS} + rm -f ${OBJS} ${TEST_OBJS} install: all mkdir -p "${DESTDIR}${BINDIR}" @@ -38,6 +44,16 @@ install: all cp -f "${NAME}.1" "${DESTDIR}${MANDIR}" chmod 644 "${DESTDIR}${MANDIR}/${NAME}.1" +depend: + for i in ${HEADERS} ${SOURCES} ${TEST_SOURCES}; do echo "$$i"; done | sort | uniq | while read i; do \ + sed -ne 's!^# *include *"\([^"]*\)".*!'"$$(echo "$$i" | sed -e 's/\.c$$/\.o/')"': '"$$(dirname "$$i" | sed -ne 's!^[^.].*!&/!; T; p; ')"'\1!; T; p' <"$$i"; \ + done | sort + +algotest.o: sha1.h +algotest.o: sha256.h +algotest.o: sha512.h +algotest.o: util.h +db.h: token.h db.o: db.h db.o: tiny-AES-c/aes.h db.o: token.h @@ -56,14 +72,12 @@ sha256.o: sha256.h sha256.o: util.h sha512.o: sha512.h sha512.o: util.h -algotest.o: sha1.h -algotest.o: sha256.h -algotest.o: sha512.h -algotest.o: util.h +tiny-AES-c/aes.o: tiny-AES-c/aes.h +token.h: util.h token.o: token.h token.o: util.h util.o: util.h -db.h: token.h -token.h: util.h -${OBJS}: Makefile +${OBJS} ${TEST_OBJS}: Makefile + +.PHONY: test depend all debug diff --git a/algotest.c b/algotest.c @@ -40,7 +40,7 @@ void test_sha1(void) if (memcmp(s.h, test_hashes[i], sizeof(s.h))) { fprintf(stderr, "%s: hash %zu mismatch, got:\n\t", - __FUNCTION__, i); + __func__, i); hexdump(stderr, s.h, sizeof(s.h)); fprintf(stderr, "\n, expected:\n\t"); hexdump(stderr, @@ -89,7 +89,7 @@ void test_hmac_sha1(void) sha1_hmac(keybuf, keylens[i], test_datas[i], strlen(test_datas[i]), hmacbuf); if (memcmp(hmacbuf, test_tags[i], taglens[i])) { - fprintf(stderr, "%s: HMAC %zu mismatch, got:\n\t", __FUNCTION__, i); + fprintf(stderr, "%s: HMAC %zu mismatch, got:\n\t", __func__, i); hexdump(stderr, hmacbuf, taglens[i]); fprintf(stderr, "\n, expected:\n\t"); hexdump(stderr, test_tags[i], taglens[i]); @@ -121,7 +121,7 @@ void test_sha224(void) sha224_finish(&s); if (memcmp(s.h, test_hashes[i], sizeof(s.h))) { - fprintf(stderr, "%s: hash %zu mismatch, got:\n\t", __FUNCTION__, i); + fprintf(stderr, "%s: hash %zu mismatch, got:\n\t", __func__, i); hexdump(stderr, s.h, sizeof(s.h)); fprintf(stderr, "\n, expected:\n\t"); hexdump(stderr, test_hashes[i], sizeof(test_hashes[i])); @@ -153,7 +153,7 @@ void test_sha256(void) sha256_finish(&s); if (memcmp(s.h, test_hashes[i], sizeof(s.h))) - fprintf(stderr, "%s: hash %zu mismatch\n", __FUNCTION__, i); + fprintf(stderr, "%s: hash %zu mismatch\n", __func__, i); } } @@ -192,7 +192,7 @@ void test_hmac_sha256(void) sha256_hmac(keybuf, keylens[i], test_datas[i], strlen(test_datas[i]), hmacbuf); if (memcmp(hmacbuf, test_tags[i], taglens[i])) { - fprintf(stderr, "%s: HMAC %zu mismatch, got:\n\t", __FUNCTION__, i); + fprintf(stderr, "%s: HMAC %zu mismatch, got:\n\t", __func__, i); hexdump(stderr, hmacbuf, taglens[i]); fprintf(stderr, "\n, expected:\n\t"); hexdump(stderr, test_tags[i], taglens[i]); @@ -226,7 +226,7 @@ void test_sha384(void) sha384_finish(&s); if (memcmp(s.h, test_hashes[i], sizeof(s.h))) - fprintf(stderr, "%s: hash %zu mismatch\n", __FUNCTION__, i); + fprintf(stderr, "%s: hash %zu mismatch\n", __func__, i); } } @@ -257,7 +257,7 @@ void test_sha512(void) sha512_finish(&s); if (memcmp(s.h, test_hashes[i], sizeof(s.h))) - fprintf(stderr, "%s: hash %zu mismatch\n", __FUNCTION__, i); + fprintf(stderr, "%s: hash %zu mismatch\n", __func__, i); } } @@ -296,7 +296,7 @@ void test_hmac_sha512(void) sha512_hmac(keybuf, keylens[i], test_datas[i], strlen(test_datas[i]), hmacbuf); if (memcmp(hmacbuf, test_tags[i], taglens[i])) { - fprintf(stderr, "%s: HMAC %zu mismatch, got:\n\t", __FUNCTION__, i); + fprintf(stderr, "%s: HMAC %zu mismatch, got:\n\t", __func__, i); hexdump(stderr, hmacbuf, taglens[i]); fprintf(stderr, "\n, expected:\n\t"); hexdump(stderr, test_tags[i], taglens[i]); @@ -326,7 +326,7 @@ void test_totp_sha1(void) if (token % modulo != totps[i]) fprintf(stderr, "%s: token %zu mismatch, got %08" PRIu32 ", expected %08" PRIu32 "\n", - __FUNCTION__, i, token % modulo, totps[i]); + __func__, i, token % modulo, totps[i]); } } @@ -351,7 +351,7 @@ void test_totp_sha256(void) if (token % modulo != totps[i]) fprintf(stderr, "%s: token %zu mismatch, got %08" PRIu32 ", expected %08" PRIu32 "\n", - __FUNCTION__, i, token % modulo, totps[i]); + __func__, i, token % modulo, totps[i]); } } @@ -376,7 +376,7 @@ void test_totp_sha512(void) if (token % modulo != totps[i]) fprintf(stderr, "%s: token %zu mismatch, got %08" PRIu32 ", expected %08" PRIu32 "\n", - __FUNCTION__, i, token % modulo, totps[i]); + __func__, i, token % modulo, totps[i]); } } @@ -403,7 +403,7 @@ void test_debase32(void) if (strcmp(buffer, plaintext[i])) fprintf(stderr, "%s: plaintext mismatch, got %s, expected %s\n", - __FUNCTION__, buffer, plaintext[i]); + __func__, buffer, plaintext[i]); } } diff --git a/db.c b/db.c @@ -15,7 +15,7 @@ struct header { uint8_t version; }; -static bool verify_db(int fd, struct AES_ctx *c) +static bool verify_db_legacy(int fd, struct AES_ctx *c, uint8_t *keybuf) { uint8_t rbuf[AES_BLOCKLEN]; int r; @@ -30,6 +30,49 @@ static bool verify_db(int fd, struct AES_ctx *c) return false; } + AES_init_ctx_iv(c, keybuf, keybuf + AES_KEYLEN); + rused = 0; + + 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; +} + +static bool verify_db(int fd, struct AES_ctx *c, uint8_t *keybuf) +{ + 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_init_ctx_iv(c, keybuf, rbuf); + rused = 0; + + 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); @@ -44,13 +87,16 @@ static bool verify_db(int fd, struct AES_ctx *c) return false; } -int db_open_read(const char *filename, struct AES_ctx *c) { +int db_open_read(const char *filename, struct AES_ctx *c, uint8_t *keybuf) { int fd = open(filename, O_RDONLY); if (fd < 0) return fd; - if (verify_db(fd, c)) + if (verify_db(fd, c, keybuf)) + return fd; + + if (!lseek(fd, 0, SEEK_SET) && verify_db_legacy(fd, c, keybuf)) return fd; close(fd); @@ -92,14 +138,31 @@ static int write_header(int fd, struct AES_ctx *c) } -int db_open_write(const char *filename, struct AES_ctx *c) +int write_salt(int fd, uint8_t *saltbuf) +{ + uint8_t *wp = saltbuf; + uint8_t * const wend = saltbuf + AES_BLOCKLEN; + ssize_t w; + + while (wp < wend && (w = write(fd, wp, wend - wp)) > 0) + wp += w; + + return wp < wend; +} + +int db_open_write(const char *filename, struct AES_ctx *c, uint8_t *keybuf) { + uint8_t saltbuf[AES_BLOCKLEN]; int fd = open(filename, O_WRONLY | O_CREAT | O_EXCL, 0600); if (fd < 0) return fd; - if (write_header(fd, c)) { + randmem(saltbuf, sizeof(saltbuf)); + AES_init_ctx_iv(c, keybuf, saltbuf); + + if (write_salt(fd, saltbuf) || + write_header(fd, c)) { int tmp_errno = errno; (void)unlink(filename); (void)close(fd); diff --git a/db.h b/db.h @@ -19,8 +19,8 @@ struct totpkey { 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); +int db_open_read(const char *filename, struct AES_ctx *c, uint8_t *keybuf); +int db_open_write(const char *filename, struct AES_ctx *c, uint8_t *keybuf); void db_foreach(int fd, struct AES_ctx *c, void (*key_cb)(struct token *token, void *data), diff --git a/main.c b/main.c @@ -229,6 +229,9 @@ int main(int argc, char *argv[]) struct generate_data gd = { NULL, false, time(NULL) }; struct token token; char *t; + struct AES_ctx c; + struct AES_ctx wc; + int wfd; ARGBEGIN { case 'l': @@ -327,16 +330,6 @@ int main(int argc, char *argv[]) } 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) { @@ -356,7 +349,7 @@ int main(int argc, char *argv[]) switch (cmd) { case CMD_LIST: - fd = db_open_read(secretfile, &c); + fd = db_open_read(secretfile, &c, keybuf); if (fd < 0) break; db_foreach(fd, &c, print_key, stdout); @@ -368,11 +361,11 @@ int main(int argc, char *argv[]) if (!token.valid) croak("Invalid uri"); - fd = db_open_read(secretfile, &c); + fd = db_open_read(secretfile, &c, keybuf); if (fd < 0 && errno != ENOENT) croak("Opening existing db failed: %s", strerror(errno)); - wfd = db_open_write(newsecretfile, &wc); + wfd = db_open_write(newsecretfile, &wc, keybuf); if (wfd < 0) croak("Could not open temporary secret file: %s", strerror(errno)); if (fd >= 0) { @@ -391,7 +384,7 @@ int main(int argc, char *argv[]) keyquery = argv[0]; /* fall-through */ case CMD_TOK: - fd = db_open_read(secretfile, &c); + fd = db_open_read(secretfile, &c, keybuf); if (fd < 0) croak("Could not open secret file: %s", strerror(errno)); gd.filter = keyquery; @@ -402,13 +395,13 @@ int main(int argc, char *argv[]) break; case CMD_DEL: - fd = db_open_read(secretfile, &c); + fd = db_open_read(secretfile, &c, keybuf); if (fd < 0) { if (errno == ENOENT) break; croak("Could not open secret file: %s", strerror(errno)); } - wfd = db_open_write(newsecretfile, &wc); + wfd = db_open_write(newsecretfile, &wc, keybuf); if (wfd < 0) croak("Could not open temporary secret file: %s", strerror(errno)); db_foreach(fd, &c, write_filter_key, @@ -422,7 +415,7 @@ int main(int argc, char *argv[]) break; case CMD_EXP: - fd = db_open_read(secretfile, &c); + fd = db_open_read(secretfile, &c, keybuf); if (fd < 0) croak("Could not open secret file: %s", strerror(errno)); db_foreach(fd, &c, print_keyuri, stdout); diff --git a/sha1.c b/sha1.c @@ -1,6 +1,7 @@ #include <stdint.h> #include <string.h> #include <stdlib.h> +#include <assert.h> #include <arpa/inet.h> @@ -66,24 +67,30 @@ static inline void _sha1_update(uint32_t *h, const void *data) 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)); + uint8_t *bw = s->buffer + (s->len & (sizeof(s->buffer) - 1)); + uint8_t *bend = 1[&s->buffer]; + + if (data == bw) { + if (bw + len == bend) + _sha1_update(s->h, s->buffer); + } else if (bw + len > bend) { + const uint8_t *d = data; + const uint8_t *dend = d + len; + + if (bw != s->buffer) { + memcpy(bw, d, bend - bw); _sha1_update(s->h, s->buffer); - d += 64 - (s->len & 63); - s->len += 64 - (s->len & 63); - len -= 64 - (s->len & 63); + d += bend - bw; } - while (len >= 64) { + + while ((size_t)(dend - d) > sizeof(s->buffer)) { _sha1_update(s->h, d); - d += 64; - s->len += 64; - len -= 64; + d += sizeof(s->buffer); } - memmove(s->buffer, d, len); + + memcpy(s->buffer, d, dend - d); } else { - memmove(s->buffer + (s->len & 63), data, len); + memcpy(bw, data, len); } s->len += len; } @@ -91,21 +98,22 @@ void sha1_update(struct sha1 *s, const void *data, size_t len) void sha1_finish(struct sha1 *s) { size_t i; + uint8_t *bw = s->buffer + (s->len & (sizeof(s->buffer) - 1)); + uint8_t *bend = 1[&s->buffer]; + + *bw++ = 0x80; + memset(bw, 0, bend - bw); - ((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)); + if (bw + sizeof(uint64_t) >= bend) { _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)); + memset(s->buffer, 0, bw - s->buffer); } - s->buffer[14] = htonl(s->len >> 29); - s->buffer[15] = htonl(s->len << 3); + + writebeu64(bend - sizeof(uint64_t), 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]); + writebeu32(&s->h[i], s->h[i]); } void sha1_hmac(const void *key, size_t keylen, diff --git a/sha1.h b/sha1.h @@ -7,7 +7,7 @@ #define SHA1_HASHSIZE 20 struct sha1 { - uint32_t buffer[16]; + uint8_t buffer[64]; uint32_t h[5]; uint64_t len; }; diff --git a/sha256.c b/sha256.c @@ -1,6 +1,7 @@ #include <stdint.h> #include <string.h> #include <stdlib.h> +#include <assert.h> #include <arpa/inet.h> @@ -76,24 +77,30 @@ static inline void _sha256_update(uint32_t *h, const void *data) 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)); + uint8_t *bw = s->buffer + (s->len & (sizeof(s->buffer) - 1)); + uint8_t *bend = 1[&s->buffer]; + + if (data == bw) { + if (bw + len == bend) + _sha256_update(s->h, s->buffer); + } else if (bw + len > bend) { + const uint8_t *d = data; + const uint8_t *dend = d + len; + + if (bw != s->buffer) { + memcpy(bw, d, bend - bw); _sha256_update(s->h, s->buffer); - d += 64 - (s->len & 63); - s->len += 64 - (s->len & 63); - len -= 64 - (s->len & 63); + d += bend - bw; } - while (len >= 64) { + + while ((size_t)(dend - d) > sizeof(s->buffer)) { _sha256_update(s->h, d); - d += 64; - s->len += 64; - len -= 64; + d += sizeof(s->buffer); } - memmove(s->buffer, d, len); + + memcpy(s->buffer, d, dend - d); } else { - memmove(s->buffer + (s->len & 63), data, len); + memcpy(bw, data, len); } s->len += len; } @@ -101,21 +108,22 @@ void sha256_update(struct sha256 *s, const void *data, size_t len) void sha256_finish(struct sha256 *s) { size_t i; + uint8_t *bw = s->buffer + (s->len & (sizeof(s->buffer) - 1)); + uint8_t *bend = 1[&s->buffer]; + + *bw++ = 0x80; + memset(bw, 0, bend - bw); - s->buffer[s->len & 63] = 0x80; - if ((s->len & 63) > 55) { - memset(s->buffer + (s->len & 63) + 1, 0, 63 - (s->len & 63)); + if (bw + sizeof(uint64_t) >= bend) { _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)); + memset(s->buffer, 0, bw - s->buffer); } - ((uint32_t *)s->buffer)[14] = htonl(s->len >> 29); - ((uint32_t *)s->buffer)[15] = htonl(s->len << 3); + + writebeu64(bend - sizeof(uint64_t), 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]); + writebeu32(&s->h[i], s->h[i]); } void sha224_init(struct sha224 *s) diff --git a/sha512.c b/sha512.c @@ -1,6 +1,7 @@ #include <stdint.h> #include <string.h> #include <stdlib.h> +#include <assert.h> #include <arpa/inet.h> @@ -89,24 +90,30 @@ static inline void _sha512_update(uint64_t *h, const void *data) 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)); + uint8_t *bw = s->buffer + (s->len & (sizeof(s->buffer) - 1)); + uint8_t *bend = 1[&s->buffer]; + + if (data == bw) { + if (bw + len == bend) + _sha512_update(s->h, s->buffer); + } else if (bw + len > bend) { + const uint8_t *d = data; + const uint8_t *dend = d + len; + + if (bw != s->buffer) { + memcpy(bw, d, bend - bw); _sha512_update(s->h, s->buffer); - d += 128 - (s->len & 127); - s->len += 128 - (s->len & 127); - len -= 128 - (s->len & 127); + d += bend - bw; } - while (len >= 128) { + + while ((size_t)(dend - d) > sizeof(s->buffer)) { _sha512_update(s->h, d); - d += 128; - s->len += 128; - len -= 128; + d += sizeof(s->buffer); } - memmove(s->buffer, d, len); + + memcpy(s->buffer, d, dend - d); } else { - memmove(s->buffer + (s->len & 127), data, len); + memcpy(bw, data, len); } s->len += len; } @@ -114,15 +121,17 @@ void sha512_update(struct sha512 *s, const void *data, size_t len) void sha512_finish(struct sha512 *s) { size_t i; + uint8_t *bw = s->buffer + (s->len & (sizeof(s->buffer) - 1)); + uint8_t *bend = 1[&s->buffer]; + + *bw++ = 0x80; + memset(bw, 0, bend - bw); - s->buffer[s->len & 127] = 0x80; - if ((s->len & 127) > 111) { - memset(s->buffer + (s->len & 127) + 1, 0, 127 - (s->len & 127)); + if (bw + 2 * sizeof(uint64_t) >= bend) { _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)); + memset(s->buffer, 0, bw - s->buffer); } + writebeu64(&s->buffer[120], s->len << 3); _sha512_update(s->h, s->buffer); diff --git a/test.sh b/test.sh @@ -1,67 +0,0 @@ -#!/bin/sh - -BIN="$1" - -trap "rm \$DB" EXIT -PASS="KhxvbPvY4dbyZ/zkXY+c/PCJ4lU" -DB="$(mktemp)" -rm "$DB" -RESULT=true - -# Secret = "12345678901234567890", algo SHA-1, period 30 seconds, 8 digits -"$BIN" -k "$PASS" -f "$DB" -a "otpauth://totp/RFC6238:SHA1?secret=GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ&issuer=RFC6238&algorithm=SHA1&digits=8&period=30" -# Secret = "12345678901234567890123456789012", algo SHA-256, period 30 seconds, 8 digits -"$BIN" -k "$PASS" -f "$DB" -a "otpauth://totp/RFC6238:SHA256?secret=GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZA&issuer=RFC6238&algorithm=SHA256&digits=8&period=30" -# Secret = "1234567890123456789012345678901234567890123456789012345678901234", algo SHA-256, period 30 seconds, 8 digits -"$BIN" -k "$PASS" -f "$DB" -a "otpauth://totp/RFC6238:SHA512?secret=GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNA&issuer=RFC6238&algorithm=SHA512&digits=8&period=30" - -while IFS='|' read _ stamp _ _ token algo _; do - algo="$(echo $algo)" - token="$(echo $token)" - stamp="$(echo $stamp)" - gentok="$("$BIN" -k "$PASS" -f "$DB" -T "$stamp" -t "RFC6238:$algo")" - if ! test "$token" = "$gentok"; then - echo "Token generation failed for $algo at time $stamp, got $gentok, expected $token" >&2 - RESULT=false - fi - read _ || break -# Test data from RFC 6238 -done <<FOO -| 59 | 1970-01-01 | 0000000000000001 | 94287082 | SHA1 | -| | 00:00:59 | | | | -| 59 | 1970-01-01 | 0000000000000001 | 46119246 | SHA256 | -| | 00:00:59 | | | | -| 59 | 1970-01-01 | 0000000000000001 | 90693936 | SHA512 | -| | 00:00:59 | | | | -| 1111111109 | 2005-03-18 | 00000000023523EC | 07081804 | SHA1 | -| | 01:58:29 | | | | -| 1111111109 | 2005-03-18 | 00000000023523EC | 68084774 | SHA256 | -| | 01:58:29 | | | | -| 1111111109 | 2005-03-18 | 00000000023523EC | 25091201 | SHA512 | -| | 01:58:29 | | | | -| 1111111111 | 2005-03-18 | 00000000023523ED | 14050471 | SHA1 | -| | 01:58:31 | | | | -| 1111111111 | 2005-03-18 | 00000000023523ED | 67062674 | SHA256 | -| | 01:58:31 | | | | -| 1111111111 | 2005-03-18 | 00000000023523ED | 99943326 | SHA512 | -| | 01:58:31 | | | | -| 1234567890 | 2009-02-13 | 000000000273EF07 | 89005924 | SHA1 | -| | 23:31:30 | | | | -| 1234567890 | 2009-02-13 | 000000000273EF07 | 91819424 | SHA256 | -| | 23:31:30 | | | | -| 1234567890 | 2009-02-13 | 000000000273EF07 | 93441116 | SHA512 | -| | 23:31:30 | | | | -| 2000000000 | 2033-05-18 | 0000000003F940AA | 69279037 | SHA1 | -| | 03:33:20 | | | | -| 2000000000 | 2033-05-18 | 0000000003F940AA | 90698825 | SHA256 | -| | 03:33:20 | | | | -| 2000000000 | 2033-05-18 | 0000000003F940AA | 38618901 | SHA512 | -| | 03:33:20 | | | | -| 20000000000 | 2603-10-11 | 0000000027BC86AA | 65353130 | SHA1 | -| | 11:33:20 | | | | -| 20000000000 | 2603-10-11 | 0000000027BC86AA | 77737706 | SHA256 | -| | 11:33:20 | | | | -| 20000000000 | 2603-10-11 | 0000000027BC86AA | 47863826 | SHA512 | -| | 11:33:20 | | | | -FOO -$RESULT diff --git a/tests/test.db b/tests/test.db Binary files differ. diff --git a/tests/test.legacy.db b/tests/test.legacy.db @@ -0,0 +1 @@ +XeRZ ܓrT)"䐒 kgVKU*ah񏒷F^GAm6,̺ h*álm82&,C>}񾻳`clΆ@`R>?@7h+хq(;g,ZtVoAvy*˱ev;G>HB,"@\,$Qj[ +\ No newline at end of file diff --git a/tests/test.pw b/tests/test.pw @@ -0,0 +1 @@ +1234567890123456789012345678901234567890123456789012345678901234 diff --git a/tests/test.sh b/tests/test.sh @@ -0,0 +1,91 @@ +#!/bin/sh + +BIN="$1" +trap "test -n \"\$TMP\" && rm \"\$TMP\"" EXIT +TMP="$(mktemp)" + +rfc6238() { + PASS="KhxvbPvY4dbyZ/zkXY+c/PCJ4lU" + rm "$TMP" + RESULT=true + + # Secret = "12345678901234567890", algo SHA-1, period 30 seconds, 8 digits + "$BIN" -k "$PASS" -f "$TMP" -a "otpauth://totp/RFC6238:SHA1?secret=GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ&issuer=RFC6238&algorithm=SHA1&digits=8&period=30" + # Secret = "12345678901234567890123456789012", algo SHA-256, period 30 seconds, 8 digits + "$BIN" -k "$PASS" -f "$TMP" -a "otpauth://totp/RFC6238:SHA256?secret=GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZA&issuer=RFC6238&algorithm=SHA256&digits=8&period=30" + # Secret = "1234567890123456789012345678901234567890123456789012345678901234", algo SHA-256, period 30 seconds, 8 digits + "$BIN" -k "$PASS" -f "$TMP" -a "otpauth://totp/RFC6238:SHA512?secret=GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNA&issuer=RFC6238&algorithm=SHA512&digits=8&period=30" + + while IFS='|' read _ stamp _ _ token algo _; do + algo="$(echo $algo)" + token="$(echo $token)" + stamp="$(echo $stamp)" + gentok="$("$BIN" -k "$PASS" -f "$TMP" -T "$stamp" -t "RFC6238:$algo")" + if ! test "$token" = "$gentok"; then + echo "Token generation failed for $algo at time $stamp, got $gentok, expected $token" >&2 + RESULT=false + fi + read _ || break + # Test data from RFC 6238 + done <<FOO +| 59 | 1970-01-01 | 0000000000000001 | 94287082 | SHA1 | +| | 00:00:59 | | | | +| 59 | 1970-01-01 | 0000000000000001 | 46119246 | SHA256 | +| | 00:00:59 | | | | +| 59 | 1970-01-01 | 0000000000000001 | 90693936 | SHA512 | +| | 00:00:59 | | | | +| 1111111109 | 2005-03-18 | 00000000023523EC | 07081804 | SHA1 | +| | 01:58:29 | | | | +| 1111111109 | 2005-03-18 | 00000000023523EC | 68084774 | SHA256 | +| | 01:58:29 | | | | +| 1111111109 | 2005-03-18 | 00000000023523EC | 25091201 | SHA512 | +| | 01:58:29 | | | | +| 1111111111 | 2005-03-18 | 00000000023523ED | 14050471 | SHA1 | +| | 01:58:31 | | | | +| 1111111111 | 2005-03-18 | 00000000023523ED | 67062674 | SHA256 | +| | 01:58:31 | | | | +| 1111111111 | 2005-03-18 | 00000000023523ED | 99943326 | SHA512 | +| | 01:58:31 | | | | +| 1234567890 | 2009-02-13 | 000000000273EF07 | 89005924 | SHA1 | +| | 23:31:30 | | | | +| 1234567890 | 2009-02-13 | 000000000273EF07 | 91819424 | SHA256 | +| | 23:31:30 | | | | +| 1234567890 | 2009-02-13 | 000000000273EF07 | 93441116 | SHA512 | +| | 23:31:30 | | | | +| 2000000000 | 2033-05-18 | 0000000003F940AA | 69279037 | SHA1 | +| | 03:33:20 | | | | +| 2000000000 | 2033-05-18 | 0000000003F940AA | 90698825 | SHA256 | +| | 03:33:20 | | | | +| 2000000000 | 2033-05-18 | 0000000003F940AA | 38618901 | SHA512 | +| | 03:33:20 | | | | +| 20000000000 | 2603-10-11 | 0000000027BC86AA | 65353130 | SHA1 | +| | 11:33:20 | | | | +| 20000000000 | 2603-10-11 | 0000000027BC86AA | 77737706 | SHA256 | +| | 11:33:20 | | | | +| 20000000000 | 2603-10-11 | 0000000027BC86AA | 47863826 | SHA512 | +| | 11:33:20 | | | | +FOO + $RESULT +} + +existdb() { + "$BIN" -K test.pw -f test.db -l >"$TMP" + diff "$TMP" - <<FOO +SHA1 by RFC6238 +SHA256 by RFC6238 +SHA512 by RFC6238 +FOO +} + +legacydb() { + "$BIN" -K test.pw -f test.legacy.db -l >"$TMP" + diff "$TMP" - <<FOO +SHA1 by RFC6238 +SHA256 by RFC6238 +SHA512 by RFC6238 +FOO +} + +rfc6238 +existdb +legacydb diff --git a/util.h b/util.h @@ -45,6 +45,16 @@ uint32_t hotp(const void *key, size_t keylen, size_t strncspn(const char *haystack, size_t haystacklen, const char *needles); +static inline void *writebeu32(void *dest, uint32_t v) +{ + uint8_t *buffer = dest; + *buffer++ = v >> 24; + *buffer++ = v >> 16; + *buffer++ = v >> 8; + *buffer++ = v; + return buffer; +} + static inline void *writebeu64(void *dest, uint64_t v) { uint8_t *buffer = dest;