totp

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

commit 709074f8d5efb2afcc13f4ca98712905277682d2
parent ca676107d5735204b3b420f1524dd6e9c8556967
Author: Santtu Lakkala <santtu.lakkala@digital14.com>
Date:   Thu, 14 Sep 2023 17:51:07 +0300

More refactoring, add simple tests

Refactor struct bytes to use end pointer instead of length. Add very
simple system tests using the RFC 6238 data, and a -T flag to request
token at custom time.

Diffstat:
MMakefile | 18+++++++++++-------
Aalgotest.c | 430+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mdb.c | 16++++++++--------
Mmain.c | 31+++++++++++++++++--------------
Dtest.c | 430-------------------------------------------------------------------------------
Atest.sh | 67+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mtoken.c | 18++++++++----------
Mutil.c | 4++--
Mutil.h | 19++++++++++++++-----
9 files changed, 557 insertions(+), 476 deletions(-)

diff --git a/Makefile b/Makefile @@ -1,8 +1,8 @@ -CFLAGS = -W -Wall -std=c99 -g -Os +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 OBJS = ${SOURCES:.c=.o} -TEST_SOURCES = sha1.c sha256.c sha512.c util.c test.c +TEST_SOURCES = sha1.c sha256.c sha512.c util.c algotest.c TEST_OBJS = ${TEST_SOURCES:.c=.o} VERSION = 0.1 @@ -17,9 +17,13 @@ all: ${NAME} ${NAME}: ${OBJS} ${CC} ${CFLAGS} -o $@ ${OBJS} ${LDFLAGS} -test: ${TEST_OBJS}; +algotest: ${TEST_OBJS}; ${CC} ${CFLAGS} -o $@ ${TEST_OBJS} ${LDFLAGS} +test: algotest ${NAME} + ./algotest + ./test.sh ./${NAME} + .c.o: ${CC} -c $< -o $@ ${CFLAGS} ${AES_CFLAGS} @@ -52,10 +56,10 @@ 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 +algotest.o: sha1.h +algotest.o: sha256.h +algotest.o: sha512.h +algotest.o: util.h token.o: token.h token.o: util.h util.o: util.h diff --git a/algotest.c b/algotest.c @@ -0,0 +1,430 @@ +#include <stdio.h> +#include <string.h> +#include <stdint.h> +#include <inttypes.h> + +#include "util.h" +#include "sha1.h" +#include "sha256.h" +#include "sha512.h" + +void hexdump(FILE *f, const void *data, size_t len) +{ + const uint8_t *d; + + for (d = data; len--; d++) + fprintf(f, "%02x", *d); +} + +void test_sha1(void) +{ + const char *test_datas[] = { + "abc", + "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq" + }; + + const uint8_t test_hashes[][SHA1_HASHSIZE] = { + { 0xA9, 0x99, 0x3E, 0x36, 0x47, 0x06, 0x81, 0x6A, 0xBA, 0x3E, + 0x25, 0x71, 0x78, 0x50, 0xC2, 0x6C, 0x9C, 0xD0, 0xD8, 0x9D, }, + { 0x84, 0x98, 0x3E, 0x44, 0x1C, 0x3B, 0xD2, 0x6E, 0xBA, 0xAE, + 0x4A, 0xA1, 0xF9, 0x51, 0x29, 0xE5, 0xE5, 0x46, 0x70, 0xF1, }, + }; + + struct sha1 s; + size_t i; + + for (i = 0; i < sizeof(test_datas) / sizeof(*test_datas); i++) { + sha1_init(&s); + sha1_update(&s, test_datas[i], strlen(test_datas[i])); + sha1_finish(&s); + + if (memcmp(s.h, test_hashes[i], sizeof(s.h))) { + fprintf(stderr, "%s: hash %zu mismatch, got:\n\t", + __FUNCTION__, i); + hexdump(stderr, s.h, sizeof(s.h)); + fprintf(stderr, "\n, expected:\n\t"); + hexdump(stderr, + test_hashes[i], sizeof(test_hashes[i])); + fprintf(stderr, "\n"); + } + } +} + +void test_hmac_sha1(void) +{ + const char *test_datas[] = { + "Sample message for keylen=blocklen", + "Sample message for keylen<blocklen", + "Sample message for keylen=blocklen", + "Sample message for keylen<blocklen, with truncated tag" + }; + + uint8_t keybuf[128]; + const size_t keylens[] = { + 64, 20, 100, 49 + }; + + const size_t taglens[] = { + 20, 20, 20, 12 + }; + + const uint8_t test_tags[][SHA1_HASHSIZE] = { + { 0x5F, 0xD5, 0x96, 0xEE, 0x78, 0xD5, 0x55, 0x3C, 0x8F, 0xF4, + 0xE7, 0x2D, 0x26, 0x6D, 0xFD, 0x19, 0x23, 0x66, 0xDA, 0x29, }, + { 0x4C, 0x99, 0xFF, 0x0C, 0xB1, 0xB3, 0x1B, 0xD3, 0x3F, 0x84, + 0x31, 0xDB, 0xAF, 0x4D, 0x17, 0xFC, 0xD3, 0x56, 0xA8, 0x07, }, + { 0x2D, 0x51, 0xB2, 0xF7, 0x75, 0x0E, 0x41, 0x05, 0x84, 0x66, + 0x2E, 0x38, 0xF1, 0x33, 0x43, 0x5F, 0x4C, 0x4F, 0xD4, 0x2A, }, + { 0xFE, 0x35, 0x29, 0x56, 0x5C, 0xD8, 0xE2, 0x8C, 0x5F, 0xA7, + 0x9E, 0xAC, }, + }; + + size_t i; + + for (i = 0; i < sizeof(keybuf); i++) + keybuf[i] = i; + + for (i = 0; i < sizeof(test_datas) / sizeof(*test_datas); i++) { + uint8_t hmacbuf[SHA1_HASHSIZE]; + 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); + hexdump(stderr, hmacbuf, taglens[i]); + fprintf(stderr, "\n, expected:\n\t"); + hexdump(stderr, test_tags[i], taglens[i]); + fprintf(stderr, "\n"); + } + } +} + +void test_sha224(void) +{ + const char *test_datas[] = { + "abc", + "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", + }; + + const uint8_t test_hashes[][SHA224_HASHSIZE] = { + { 0x23, 0x09, 0x7D, 0x22, 0x34, 0x05, 0xD8, 0x22, 0x86, 0x42, 0xA4, 0x77, 0xBD, 0xA2, + 0x55, 0xB3, 0x2A, 0xAD, 0xBC, 0xE4, 0xBD, 0xA0, 0xB3, 0xF7, 0xE3, 0x6C, 0x9D, 0xA7, }, + { 0x75, 0x38, 0x8B, 0x16, 0x51, 0x27, 0x76, 0xCC, 0x5D, 0xBA, 0x5D, 0xA1, 0xFD, 0x89, + 0x01, 0x50, 0xB0, 0xC6, 0x45, 0x5C, 0xB4, 0xF5, 0x8B, 0x19, 0x52, 0x52, 0x25, 0x25, }, + }; + + struct sha224 s; + size_t i; + + for (i = 0; i < sizeof(test_datas) / sizeof(*test_datas); i++) { + sha224_init(&s); + sha224_update(&s, test_datas[i], strlen(test_datas[i])); + sha224_finish(&s); + + if (memcmp(s.h, test_hashes[i], sizeof(s.h))) { + fprintf(stderr, "%s: hash %zu mismatch, got:\n\t", __FUNCTION__, i); + hexdump(stderr, s.h, sizeof(s.h)); + fprintf(stderr, "\n, expected:\n\t"); + hexdump(stderr, test_hashes[i], sizeof(test_hashes[i])); + fprintf(stderr, "\n"); + } + } +} + +void test_sha256(void) +{ + const char *test_datas[] = { + "abc", + "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq" + }; + + const uint8_t test_hashes[][SHA256_HASHSIZE] = { + { 0xBA, 0x78, 0x16, 0xBF, 0x8F, 0x01, 0xCF, 0xEA, 0x41, 0x41, 0x40, 0xDE, 0x5D, 0xAE, 0x22, 0x23, + 0xB0, 0x03, 0x61, 0xA3, 0x96, 0x17, 0x7A, 0x9C, 0xB4, 0x10, 0xFF, 0x61, 0xF2, 0x00, 0x15, 0xAD, }, + { 0x24, 0x8D, 0x6A, 0x61, 0xD2, 0x06, 0x38, 0xB8, 0xE5, 0xC0, 0x26, 0x93, 0x0C, 0x3E, 0x60, 0x39, + 0xA3, 0x3C, 0xE4, 0x59, 0x64, 0xFF, 0x21, 0x67, 0xF6, 0xEC, 0xED, 0xD4, 0x19, 0xDB, 0x06, 0xC1, }, + }; + + struct sha256 s; + size_t i; + + for (i = 0; i < sizeof(test_datas) / sizeof(*test_datas); i++) { + sha256_init(&s); + sha256_update(&s, test_datas[i], strlen(test_datas[i])); + sha256_finish(&s); + + if (memcmp(s.h, test_hashes[i], sizeof(s.h))) + fprintf(stderr, "%s: hash %zu mismatch\n", __FUNCTION__, i); + } +} + +void test_hmac_sha256(void) +{ + const char *test_datas[] = { + "Sample message for keylen=blocklen", + "Sample message for keylen<blocklen", + "Sample message for keylen=blocklen", + "Sample message for keylen<blocklen, with truncated tag" + }; + + uint8_t keybuf[128]; + const size_t keylens[] = { + 64, 32, 100, 49 + }; + + const size_t taglens[] = { + 32, 32, 32, 16 + }; + + const uint8_t test_tags[][SHA256_HASHSIZE] = { + { 0x8B, 0xB9, 0xA1, 0xDB, 0x98, 0x06, 0xF2, 0x0D, 0xF7, 0xF7, 0x7B, 0x82, 0x13, 0x8C, 0x79, 0x14, 0xD1, 0x74, 0xD5, 0x9E, 0x13, 0xDC, 0x4D, 0x01, 0x69, 0xC9, 0x05, 0x7B, 0x13, 0x3E, 0x1D, 0x62, }, + { 0xA2, 0x8C, 0xF4, 0x31, 0x30, 0xEE, 0x69, 0x6A, 0x98, 0xF1, 0x4A, 0x37, 0x67, 0x8B, 0x56, 0xBC, 0xFC, 0xBD, 0xD9, 0xE5, 0xCF, 0x69, 0x71, 0x7F, 0xEC, 0xF5, 0x48, 0x0F, 0x0E, 0xBD, 0xF7, 0x90, }, + { 0xBD, 0xCC, 0xB6, 0xC7, 0x2D, 0xDE, 0xAD, 0xB5, 0x00, 0xAE, 0x76, 0x83, 0x86, 0xCB, 0x38, 0xCC, 0x41, 0xC6, 0x3D, 0xBB, 0x08, 0x78, 0xDD, 0xB9, 0xC7, 0xA3, 0x8A, 0x43, 0x1B, 0x78, 0x37, 0x8D, }, + { 0x27, 0xA8, 0xB1, 0x57, 0x83, 0x9E, 0xFE, 0xAC, 0x98, 0xDF, 0x07, 0x0B, 0x33, 0x1D, 0x59, 0x36, }, + }; + + size_t i; + + for (i = 0; i < sizeof(keybuf); i++) + keybuf[i] = i; + + for (i = 0; i < sizeof(test_datas) / sizeof(*test_datas); i++) { + uint8_t hmacbuf[SHA256_HASHSIZE]; + 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); + hexdump(stderr, hmacbuf, taglens[i]); + fprintf(stderr, "\n, expected:\n\t"); + hexdump(stderr, test_tags[i], taglens[i]); + fprintf(stderr, "\n"); + } + } +} + +void test_sha384(void) +{ + const char *test_datas[] = { + "abc", + "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu", + }; + + const uint8_t test_hashes[][SHA384_HASHSIZE] = { + { 0xCB, 0x00, 0x75, 0x3F, 0x45, 0xA3, 0x5E, 0x8B, 0xB5, 0xA0, 0x3D, 0x69, 0x9A, 0xC6, 0x50, 0x07, + 0x27, 0x2C, 0x32, 0xAB, 0x0E, 0xDE, 0xD1, 0x63, 0x1A, 0x8B, 0x60, 0x5A, 0x43, 0xFF, 0x5B, 0xED, + 0x80, 0x86, 0x07, 0x2B, 0xA1, 0xE7, 0xCC, 0x23, 0x58, 0xBA, 0xEC, 0xA1, 0x34, 0xC8, 0x25, 0xA7, }, + { 0x09, 0x33, 0x0C, 0x33, 0xF7, 0x11, 0x47, 0xE8, 0x3D, 0x19, 0x2F, 0xC7, 0x82, 0xCD, 0x1B, 0x47, + 0x53, 0x11, 0x1B, 0x17, 0x3B, 0x3B, 0x05, 0xD2, 0x2F, 0xA0, 0x80, 0x86, 0xE3, 0xB0, 0xF7, 0x12, + 0xFC, 0xC7, 0xC7, 0x1A, 0x55, 0x7E, 0x2D, 0xB9, 0x66, 0xC3, 0xE9, 0xFA, 0x91, 0x74, 0x60, 0x39, }, + }; + + struct sha384 s; + size_t i; + + for (i = 0; i < sizeof(test_datas) / sizeof(*test_datas); i++) { + sha384_init(&s); + sha384_update(&s, test_datas[i], strlen(test_datas[i])); + sha384_finish(&s); + + if (memcmp(s.h, test_hashes[i], sizeof(s.h))) + fprintf(stderr, "%s: hash %zu mismatch\n", __FUNCTION__, i); + } +} + +void test_sha512(void) +{ + const char *test_datas[] = { + "abc", + "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu", + }; + + const uint8_t test_hashes[][SHA512_HASHSIZE] = { + { 0xDD, 0xAF, 0x35, 0xA1, 0x93, 0x61, 0x7A, 0xBA, 0xCC, 0x41, 0x73, 0x49, 0xAE, 0x20, 0x41, 0x31, + 0x12, 0xE6, 0xFA, 0x4E, 0x89, 0xA9, 0x7E, 0xA2, 0x0A, 0x9E, 0xEE, 0xE6, 0x4B, 0x55, 0xD3, 0x9A, + 0x21, 0x92, 0x99, 0x2A, 0x27, 0x4F, 0xC1, 0xA8, 0x36, 0xBA, 0x3C, 0x23, 0xA3, 0xFE, 0xEB, 0xBD, + 0x45, 0x4D, 0x44, 0x23, 0x64, 0x3C, 0xE8, 0x0E, 0x2A, 0x9A, 0xC9, 0x4F, 0xA5, 0x4C, 0xA4, 0x9F, }, + { 0x8E, 0x95, 0x9B, 0x75, 0xDA, 0xE3, 0x13, 0xDA, 0x8C, 0xF4, 0xF7, 0x28, 0x14, 0xFC, 0x14, 0x3F, + 0x8F, 0x77, 0x79, 0xC6, 0xEB, 0x9F, 0x7F, 0xA1, 0x72, 0x99, 0xAE, 0xAD, 0xB6, 0x88, 0x90, 0x18, + 0x50, 0x1D, 0x28, 0x9E, 0x49, 0x00, 0xF7, 0xE4, 0x33, 0x1B, 0x99, 0xDE, 0xC4, 0xB5, 0x43, 0x3A, + 0xC7, 0xD3, 0x29, 0xEE, 0xB6, 0xDD, 0x26, 0x54, 0x5E, 0x96, 0xE5, 0x5B, 0x87, 0x4B, 0xE9, 0x09, }, + }; + + struct sha512 s; + size_t i; + + for (i = 0; i < sizeof(test_datas) / sizeof(*test_datas); i++) { + sha512_init(&s); + sha512_update(&s, test_datas[i], strlen(test_datas[i])); + sha512_finish(&s); + + if (memcmp(s.h, test_hashes[i], sizeof(s.h))) + fprintf(stderr, "%s: hash %zu mismatch\n", __FUNCTION__, i); + } +} + +void test_hmac_sha512(void) +{ + const char *test_datas[] = { + "Sample message for keylen=blocklen", + "Sample message for keylen<blocklen", + "Sample message for keylen=blocklen", + "Sample message for keylen<blocklen, with truncated tag" + }; + + uint8_t keybuf[256]; + const size_t keylens[] = { + 128, 64, 200, 49 + }; + + const size_t taglens[] = { + 64, 64, 64, 32 + }; + + const uint8_t test_tags[][SHA512_HASHSIZE] = { + { 0xFC, 0x25, 0xE2, 0x40, 0x65, 0x8C, 0xA7, 0x85, 0xB7, 0xA8, 0x11, 0xA8, 0xD3, 0xF7, 0xB4, 0xCA, 0x48, 0xCF, 0xA2, 0x6A, 0x8A, 0x36, 0x6B, 0xF2, 0xCD, 0x1F, 0x83, 0x6B, 0x05, 0xFC, 0xB0, 0x24, 0xBD, 0x36, 0x85, 0x30, 0x81, 0x81, 0x1D, 0x6C, 0xEA, 0x42, 0x16, 0xEB, 0xAD, 0x79, 0xDA, 0x1C, 0xFC, 0xB9, 0x5E, 0xA4, 0x58, 0x6B, 0x8A, 0x0C, 0xE3, 0x56, 0x59, 0x6A, 0x55, 0xFB, 0x13, 0x47, }, + { 0xFD, 0x44, 0xC1, 0x8B, 0xDA, 0x0B, 0xB0, 0xA6, 0xCE, 0x0E, 0x82, 0xB0, 0x31, 0xBF, 0x28, 0x18, 0xF6, 0x53, 0x9B, 0xD5, 0x6E, 0xC0, 0x0B, 0xDC, 0x10, 0xA8, 0xA2, 0xD7, 0x30, 0xB3, 0x63, 0x4D, 0xE2, 0x54, 0x5D, 0x63, 0x9B, 0x0F, 0x2C, 0xF7, 0x10, 0xD0, 0x69, 0x2C, 0x72, 0xA1, 0x89, 0x6F, 0x1F, 0x21, 0x1C, 0x2B, 0x92, 0x2D, 0x1A, 0x96, 0xC3, 0x92, 0xE0, 0x7E, 0x7E, 0xA9, 0xFE, 0xDC, }, + { 0xD9, 0x3E, 0xC8, 0xD2, 0xDE, 0x1A, 0xD2, 0xA9, 0x95, 0x7C, 0xB9, 0xB8, 0x3F, 0x14, 0xE7, 0x6A, 0xD6, 0xB5, 0xE0, 0xCC, 0xE2, 0x85, 0x07, 0x9A, 0x12, 0x7D, 0x3B, 0x14, 0xBC, 0xCB, 0x7A, 0xA7, 0x28, 0x6D, 0x4A, 0xC0, 0xD4, 0xCE, 0x64, 0x21, 0x5F, 0x2B, 0xC9, 0xE6, 0x87, 0x0B, 0x33, 0xD9, 0x74, 0x38, 0xBE, 0x4A, 0xAA, 0x20, 0xCD, 0xA5, 0xC5, 0xA9, 0x12, 0xB4, 0x8B, 0x8E, 0x27, 0xF3, }, + { 0x00, 0xF3, 0xE9, 0xA7, 0x7B, 0xB0, 0xF0, 0x6D, 0xE1, 0x5F, 0x16, 0x06, 0x03, 0xE4, 0x2B, 0x50, 0x28, 0x75, 0x88, 0x08, 0x59, 0x66, 0x64, 0xC0, 0x3E, 0x1A, 0xB8, 0xFB, 0x2B, 0x07, 0x67, 0x78, }, + }; + + size_t i; + + for (i = 0; i < sizeof(keybuf); i++) + keybuf[i] = i; + + for (i = 0; i < sizeof(test_datas) / sizeof(*test_datas); i++) { + uint8_t hmacbuf[SHA512_HASHSIZE]; + 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); + hexdump(stderr, hmacbuf, taglens[i]); + fprintf(stderr, "\n, expected:\n\t"); + hexdump(stderr, test_tags[i], taglens[i]); + fprintf(stderr, "\n"); + } + } +} + +void test_totp_sha1(void) +{ + /* Test vectors from RFC 6238 appendix B */ + const char *key = "12345678901234567890"; + const uint8_t period = 30; + const time_t t0 = 0; + + const time_t times[] = { + 59, 1111111109, 1111111111, 1234567890, 2000000000, 20000000000 + }; + const uint32_t totps[] = { + 94287082, /*0*/7081804, 14050471, 89005924, 69279037, 65353130 + }; + const uint32_t modulo = 100000000; + size_t i; + + for (i = 0; i < sizeof(times) / sizeof(*times); i++) { + uint32_t token = totp(key, strlen(key), times[i], period, t0, sha1_hmac, SHA1_HASHSIZE); + + if (token % modulo != totps[i]) + fprintf(stderr, "%s: token %zu mismatch, got %08" PRIu32 ", expected %08" PRIu32 "\n", + __FUNCTION__, i, token % modulo, totps[i]); + } +} + +void test_totp_sha256(void) +{ + /* Test vectors from RFC 6238 appendix B but key/seed from appendix A */ + const char *key = "12345678901234567890123456789012"; + const uint8_t period = 30; + const time_t t0 = 0; + + const time_t times[] = { + 59, 1111111109, 1111111111, 1234567890, 2000000000, 20000000000 + }; + const uint32_t totps[] = { + 46119246, 68084774, 67062674, 91819424, 90698825, 77737706 + }; + const uint32_t modulo = 100000000; + size_t i; + + for (i = 0; i < sizeof(times) / sizeof(*times); i++) { + uint32_t token = totp(key, strlen(key), times[i], period, t0, sha256_hmac, SHA256_HASHSIZE); + + if (token % modulo != totps[i]) + fprintf(stderr, "%s: token %zu mismatch, got %08" PRIu32 ", expected %08" PRIu32 "\n", + __FUNCTION__, i, token % modulo, totps[i]); + } +} + +void test_totp_sha512(void) +{ + /* Test vectors from RFC 6238 appendix B but key/seed from appendix A */ + const char *key = "1234567890123456789012345678901234567890123456789012345678901234"; + const uint8_t period = 30; + const time_t t0 = 0; + + const time_t times[] = { + 59, 1111111109, 1111111111, 1234567890, 2000000000, 20000000000 + }; + const uint32_t totps[] = { + 90693936, 25091201, 99943326, 93441116, 38618901, 47863826 + }; + const uint32_t modulo = 100000000; + size_t i; + + for (i = 0; i < sizeof(times) / sizeof(*times); i++) { + uint32_t token = totp(key, strlen(key), times[i], period, t0, sha512_hmac, SHA512_HASHSIZE); + + if (token % modulo != totps[i]) + fprintf(stderr, "%s: token %zu mismatch, got %08" PRIu32 ", expected %08" PRIu32 "\n", + __FUNCTION__, i, token % modulo, totps[i]); + } +} + +void test_debase32(void) +{ + const char *base32s[] = { + "MFRGG", + "MFRGGZDFMZTWQ2LKNM", + "MFRGGZDF", + }; + + const char *plaintext[] = { + "abc", + "abcdefghijk", + "abcde" + }; + + size_t i; + + for (i = 0; i < sizeof(base32s) / sizeof(*base32s); i++) { + char buffer[64]; + int len = sprintf(buffer, "%s", base32s[i]); + *(char *)debase32(bytesnc(buffer, len)).end = '\0'; + + if (strcmp(buffer, plaintext[i])) + fprintf(stderr, "%s: plaintext mismatch, got %s, expected %s\n", + __FUNCTION__, buffer, plaintext[i]); + } +} + +int main(int argc, char **argv) +{ + (void)argc; + (void)argv; + + test_sha1(); + test_sha224(); + test_sha256(); + test_sha384(); + test_sha512(); + + test_hmac_sha1(); + test_hmac_sha256(); + test_hmac_sha512(); + + test_totp_sha1(); + test_totp_sha256(); + test_totp_sha512(); + + test_debase32(); +} diff --git a/db.c b/db.c @@ -137,9 +137,9 @@ void db_foreach(int fd, struct AES_ctx *c, 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 }; + struct bytes key = bytesnc(&kh[1], kh->keylen); + struct bytes desc = bytesnc(key.end, kh->desclen); + struct bytes issuer = bytesnc(desc.end, kh->issuerlen); key_cb(&(struct token){ key, desc, issuer, kh->digest, readbeu64(kh->t0), @@ -154,13 +154,13 @@ void db_foreach(int fd, struct AES_ctx *c, 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 ksz = sizeof(struct totpkey) + bytes_len(token->key) + bytes_len(token->desc) + bytes_len(token->issuer); 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) { + if (bytes_len(token->key) > UINT8_MAX || bytes_len(token->desc) > UINT8_MAX || bytes_len(token->issuer) > UINT8_MAX) { errno = EMSGSIZE; return -1; } @@ -173,9 +173,9 @@ int db_add_key(int fd, struct AES_ctx *c, .digest = token->digest, .digits = token->digits, .period = token->period, - .keylen = token->key.len, - .desclen = token->desc.len, - .issuerlen = token->issuer.len }, sizeof(struct totpkey)); + .keylen = bytes_len(token->key), + .desclen = bytes_len(token->desc), + .issuerlen = bytes_len(token->issuer) }, sizeof(struct totpkey)); wp = mempushb(wp, token->key); wp = mempushb(wp, token->desc); wp = mempushb(wp, token->issuer); diff --git a/main.c b/main.c @@ -51,11 +51,10 @@ 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; - while (len--) { + while (buffer < data.end) { v = v << 8 | *buffer++; b += 8; while (b >= 5) { @@ -73,14 +72,14 @@ void print_key(struct token *token, void *data) (void)data; - fprintf(stream, "%.*s by %.*s\n", (int)token->desc.len, token->desc.data, (int)token->issuer.len, token->issuer.data); + fprintf(stream, "%.*s by %.*s\n", (int)bytes_len(token->desc), token->desc.data, (int)bytes_len(token->issuer), token->issuer.data); } static void print_uriencode(FILE *stream, struct bytes data, bool getarg) { const char *escape = ":/@+% &?"; - const char *buf = (const char *)data.data; - const char *end = buf + data.len; + const char *buf = (const void *)data.data; + const char *end = (const void *)data.end; while (buf < end && *buf) { size_t pass = strncspn(buf, end - buf, escape); printf("%.*s", (int)pass, buf); @@ -102,14 +101,14 @@ void print_keyuri(struct token *token, FILE *stream = data; fputs("otpauth://totp/", stream); - if (token->issuer.len) { + if (bytes_len(token->issuer)) { 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) { + if (bytes_len(token->issuer)) { fputs("&issuer=", stream); print_uriencode(stream, token->issuer, true); } @@ -122,6 +121,7 @@ void print_keyuri(struct token *token, struct generate_data { const char *filter; bool found; + time_t time; }; void generate_token(struct token *token, void *data) @@ -132,11 +132,11 @@ void generate_token(struct token *token, void *data) char descbuf[512]; char *dp = descbuf; - if (token->issuer.len) { - dp = mempush(dp, token->issuer.data, token->issuer.len); + if (bytes_len(token->issuer)) { + dp = mempushb(dp, token->issuer); *dp++ = ':'; } - dp = mempush(dp, token->desc.data, token->desc.len); + dp = mempushb(dp, token->desc); *dp = '\0'; if (fnmatch(d->filter, descbuf, FNM_NOESCAPE)) @@ -147,7 +147,7 @@ void generate_token(struct token *token, void *data) modulo *= 10; 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); + totp(token->key.data, bytes_len(token->key), d->time, token->period, token->t0, digest_hmacs[token->digest], digest_sizes[token->digest]) % modulo); } struct write_filter_data { @@ -164,8 +164,7 @@ void write_filter_key(struct token *token, if (d->filter) { char descbuf[UINT8_MAX + 1]; - memcpy(descbuf, token->desc.data, token->desc.len); - descbuf[token->desc.len] = '\0'; + *(char *)mempushb(descbuf, token->desc) = '\0'; if (!fnmatch(d->filter, descbuf, FNM_NOESCAPE)) return; @@ -190,6 +189,7 @@ void usage() "-f <file>\tuse file as database\n" "-k <pass>\tpassphrase for database encryption\n" "-K <file>\tread encryption passphrase from file\n" + "-T <time>\tunix time for token generation\n" "-l\tlist known secrets\n" "-a <uri>\tadd uri to secrets\n" "-d <filter>\tremove secrets matching filter\n" @@ -226,7 +226,7 @@ int main(int argc, char *argv[]) bool free_secretfile = true; uint8_t keybuf[AES_KEYLEN + AES_BLOCKLEN]; size_t keylen = 0; - struct generate_data gd = { NULL, false }; + struct generate_data gd = { NULL, false, time(NULL) }; struct token token; char *t; @@ -246,6 +246,9 @@ int main(int argc, char *argv[]) cmd = CMD_TOK; keyquery = EARGF(usage()); break; + case 'T': + gd.time = strtoull(EARGF(usage()), NULL, 10); + break; case 'e': cmd = CMD_EXP; break; diff --git a/test.c b/test.c @@ -1,430 +0,0 @@ -#include <stdio.h> -#include <string.h> -#include <stdint.h> -#include <inttypes.h> - -#include "util.h" -#include "sha1.h" -#include "sha256.h" -#include "sha512.h" - -void hexdump(FILE *f, const void *data, size_t len) -{ - const uint8_t *d; - - for (d = data; len--; d++) - fprintf(f, "%02x", *d); -} - -void test_sha1(void) -{ - const char *test_datas[] = { - "abc", - "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq" - }; - - const uint8_t test_hashes[][SHA1_HASHSIZE] = { - { 0xA9, 0x99, 0x3E, 0x36, 0x47, 0x06, 0x81, 0x6A, 0xBA, 0x3E, - 0x25, 0x71, 0x78, 0x50, 0xC2, 0x6C, 0x9C, 0xD0, 0xD8, 0x9D, }, - { 0x84, 0x98, 0x3E, 0x44, 0x1C, 0x3B, 0xD2, 0x6E, 0xBA, 0xAE, - 0x4A, 0xA1, 0xF9, 0x51, 0x29, 0xE5, 0xE5, 0x46, 0x70, 0xF1, }, - }; - - struct sha1 s; - size_t i; - - for (i = 0; i < sizeof(test_datas) / sizeof(*test_datas); i++) { - sha1_init(&s); - sha1_update(&s, test_datas[i], strlen(test_datas[i])); - sha1_finish(&s); - - if (memcmp(s.h, test_hashes[i], sizeof(s.h))) { - fprintf(stderr, "%s: hash %zu mismatch, got:\n\t", - __FUNCTION__, i); - hexdump(stderr, s.h, sizeof(s.h)); - fprintf(stderr, "\n, expected:\n\t"); - hexdump(stderr, - test_hashes[i], sizeof(test_hashes[i])); - fprintf(stderr, "\n"); - } - } -} - -void test_hmac_sha1(void) -{ - const char *test_datas[] = { - "Sample message for keylen=blocklen", - "Sample message for keylen<blocklen", - "Sample message for keylen=blocklen", - "Sample message for keylen<blocklen, with truncated tag" - }; - - uint8_t keybuf[128]; - const size_t keylens[] = { - 64, 20, 100, 49 - }; - - const size_t taglens[] = { - 20, 20, 20, 12 - }; - - const uint8_t test_tags[][SHA1_HASHSIZE] = { - { 0x5F, 0xD5, 0x96, 0xEE, 0x78, 0xD5, 0x55, 0x3C, 0x8F, 0xF4, - 0xE7, 0x2D, 0x26, 0x6D, 0xFD, 0x19, 0x23, 0x66, 0xDA, 0x29, }, - { 0x4C, 0x99, 0xFF, 0x0C, 0xB1, 0xB3, 0x1B, 0xD3, 0x3F, 0x84, - 0x31, 0xDB, 0xAF, 0x4D, 0x17, 0xFC, 0xD3, 0x56, 0xA8, 0x07, }, - { 0x2D, 0x51, 0xB2, 0xF7, 0x75, 0x0E, 0x41, 0x05, 0x84, 0x66, - 0x2E, 0x38, 0xF1, 0x33, 0x43, 0x5F, 0x4C, 0x4F, 0xD4, 0x2A, }, - { 0xFE, 0x35, 0x29, 0x56, 0x5C, 0xD8, 0xE2, 0x8C, 0x5F, 0xA7, - 0x9E, 0xAC, }, - }; - - size_t i; - - for (i = 0; i < sizeof(keybuf); i++) - keybuf[i] = i; - - for (i = 0; i < sizeof(test_datas) / sizeof(*test_datas); i++) { - uint8_t hmacbuf[SHA1_HASHSIZE]; - 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); - hexdump(stderr, hmacbuf, taglens[i]); - fprintf(stderr, "\n, expected:\n\t"); - hexdump(stderr, test_tags[i], taglens[i]); - fprintf(stderr, "\n"); - } - } -} - -void test_sha224(void) -{ - const char *test_datas[] = { - "abc", - "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", - }; - - const uint8_t test_hashes[][SHA224_HASHSIZE] = { - { 0x23, 0x09, 0x7D, 0x22, 0x34, 0x05, 0xD8, 0x22, 0x86, 0x42, 0xA4, 0x77, 0xBD, 0xA2, - 0x55, 0xB3, 0x2A, 0xAD, 0xBC, 0xE4, 0xBD, 0xA0, 0xB3, 0xF7, 0xE3, 0x6C, 0x9D, 0xA7, }, - { 0x75, 0x38, 0x8B, 0x16, 0x51, 0x27, 0x76, 0xCC, 0x5D, 0xBA, 0x5D, 0xA1, 0xFD, 0x89, - 0x01, 0x50, 0xB0, 0xC6, 0x45, 0x5C, 0xB4, 0xF5, 0x8B, 0x19, 0x52, 0x52, 0x25, 0x25, }, - }; - - struct sha224 s; - size_t i; - - for (i = 0; i < sizeof(test_datas) / sizeof(*test_datas); i++) { - sha224_init(&s); - sha224_update(&s, test_datas[i], strlen(test_datas[i])); - sha224_finish(&s); - - if (memcmp(s.h, test_hashes[i], sizeof(s.h))) { - fprintf(stderr, "%s: hash %zu mismatch, got:\n\t", __FUNCTION__, i); - hexdump(stderr, s.h, sizeof(s.h)); - fprintf(stderr, "\n, expected:\n\t"); - hexdump(stderr, test_hashes[i], sizeof(test_hashes[i])); - fprintf(stderr, "\n"); - } - } -} - -void test_sha256(void) -{ - const char *test_datas[] = { - "abc", - "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq" - }; - - const uint8_t test_hashes[][SHA256_HASHSIZE] = { - { 0xBA, 0x78, 0x16, 0xBF, 0x8F, 0x01, 0xCF, 0xEA, 0x41, 0x41, 0x40, 0xDE, 0x5D, 0xAE, 0x22, 0x23, - 0xB0, 0x03, 0x61, 0xA3, 0x96, 0x17, 0x7A, 0x9C, 0xB4, 0x10, 0xFF, 0x61, 0xF2, 0x00, 0x15, 0xAD, }, - { 0x24, 0x8D, 0x6A, 0x61, 0xD2, 0x06, 0x38, 0xB8, 0xE5, 0xC0, 0x26, 0x93, 0x0C, 0x3E, 0x60, 0x39, - 0xA3, 0x3C, 0xE4, 0x59, 0x64, 0xFF, 0x21, 0x67, 0xF6, 0xEC, 0xED, 0xD4, 0x19, 0xDB, 0x06, 0xC1, }, - }; - - struct sha256 s; - size_t i; - - for (i = 0; i < sizeof(test_datas) / sizeof(*test_datas); i++) { - sha256_init(&s); - sha256_update(&s, test_datas[i], strlen(test_datas[i])); - sha256_finish(&s); - - if (memcmp(s.h, test_hashes[i], sizeof(s.h))) - fprintf(stderr, "%s: hash %zu mismatch\n", __FUNCTION__, i); - } -} - -void test_hmac_sha256(void) -{ - const char *test_datas[] = { - "Sample message for keylen=blocklen", - "Sample message for keylen<blocklen", - "Sample message for keylen=blocklen", - "Sample message for keylen<blocklen, with truncated tag" - }; - - uint8_t keybuf[128]; - const size_t keylens[] = { - 64, 32, 100, 49 - }; - - const size_t taglens[] = { - 32, 32, 32, 16 - }; - - const uint8_t test_tags[][SHA256_HASHSIZE] = { - { 0x8B, 0xB9, 0xA1, 0xDB, 0x98, 0x06, 0xF2, 0x0D, 0xF7, 0xF7, 0x7B, 0x82, 0x13, 0x8C, 0x79, 0x14, 0xD1, 0x74, 0xD5, 0x9E, 0x13, 0xDC, 0x4D, 0x01, 0x69, 0xC9, 0x05, 0x7B, 0x13, 0x3E, 0x1D, 0x62, }, - { 0xA2, 0x8C, 0xF4, 0x31, 0x30, 0xEE, 0x69, 0x6A, 0x98, 0xF1, 0x4A, 0x37, 0x67, 0x8B, 0x56, 0xBC, 0xFC, 0xBD, 0xD9, 0xE5, 0xCF, 0x69, 0x71, 0x7F, 0xEC, 0xF5, 0x48, 0x0F, 0x0E, 0xBD, 0xF7, 0x90, }, - { 0xBD, 0xCC, 0xB6, 0xC7, 0x2D, 0xDE, 0xAD, 0xB5, 0x00, 0xAE, 0x76, 0x83, 0x86, 0xCB, 0x38, 0xCC, 0x41, 0xC6, 0x3D, 0xBB, 0x08, 0x78, 0xDD, 0xB9, 0xC7, 0xA3, 0x8A, 0x43, 0x1B, 0x78, 0x37, 0x8D, }, - { 0x27, 0xA8, 0xB1, 0x57, 0x83, 0x9E, 0xFE, 0xAC, 0x98, 0xDF, 0x07, 0x0B, 0x33, 0x1D, 0x59, 0x36, }, - }; - - size_t i; - - for (i = 0; i < sizeof(keybuf); i++) - keybuf[i] = i; - - for (i = 0; i < sizeof(test_datas) / sizeof(*test_datas); i++) { - uint8_t hmacbuf[SHA256_HASHSIZE]; - 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); - hexdump(stderr, hmacbuf, taglens[i]); - fprintf(stderr, "\n, expected:\n\t"); - hexdump(stderr, test_tags[i], taglens[i]); - fprintf(stderr, "\n"); - } - } -} - -void test_sha384(void) -{ - const char *test_datas[] = { - "abc", - "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu", - }; - - const uint8_t test_hashes[][SHA384_HASHSIZE] = { - { 0xCB, 0x00, 0x75, 0x3F, 0x45, 0xA3, 0x5E, 0x8B, 0xB5, 0xA0, 0x3D, 0x69, 0x9A, 0xC6, 0x50, 0x07, - 0x27, 0x2C, 0x32, 0xAB, 0x0E, 0xDE, 0xD1, 0x63, 0x1A, 0x8B, 0x60, 0x5A, 0x43, 0xFF, 0x5B, 0xED, - 0x80, 0x86, 0x07, 0x2B, 0xA1, 0xE7, 0xCC, 0x23, 0x58, 0xBA, 0xEC, 0xA1, 0x34, 0xC8, 0x25, 0xA7, }, - { 0x09, 0x33, 0x0C, 0x33, 0xF7, 0x11, 0x47, 0xE8, 0x3D, 0x19, 0x2F, 0xC7, 0x82, 0xCD, 0x1B, 0x47, - 0x53, 0x11, 0x1B, 0x17, 0x3B, 0x3B, 0x05, 0xD2, 0x2F, 0xA0, 0x80, 0x86, 0xE3, 0xB0, 0xF7, 0x12, - 0xFC, 0xC7, 0xC7, 0x1A, 0x55, 0x7E, 0x2D, 0xB9, 0x66, 0xC3, 0xE9, 0xFA, 0x91, 0x74, 0x60, 0x39, }, - }; - - struct sha384 s; - size_t i; - - for (i = 0; i < sizeof(test_datas) / sizeof(*test_datas); i++) { - sha384_init(&s); - sha384_update(&s, test_datas[i], strlen(test_datas[i])); - sha384_finish(&s); - - if (memcmp(s.h, test_hashes[i], sizeof(s.h))) - fprintf(stderr, "%s: hash %zu mismatch\n", __FUNCTION__, i); - } -} - -void test_sha512(void) -{ - const char *test_datas[] = { - "abc", - "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu", - }; - - const uint8_t test_hashes[][SHA512_HASHSIZE] = { - { 0xDD, 0xAF, 0x35, 0xA1, 0x93, 0x61, 0x7A, 0xBA, 0xCC, 0x41, 0x73, 0x49, 0xAE, 0x20, 0x41, 0x31, - 0x12, 0xE6, 0xFA, 0x4E, 0x89, 0xA9, 0x7E, 0xA2, 0x0A, 0x9E, 0xEE, 0xE6, 0x4B, 0x55, 0xD3, 0x9A, - 0x21, 0x92, 0x99, 0x2A, 0x27, 0x4F, 0xC1, 0xA8, 0x36, 0xBA, 0x3C, 0x23, 0xA3, 0xFE, 0xEB, 0xBD, - 0x45, 0x4D, 0x44, 0x23, 0x64, 0x3C, 0xE8, 0x0E, 0x2A, 0x9A, 0xC9, 0x4F, 0xA5, 0x4C, 0xA4, 0x9F, }, - { 0x8E, 0x95, 0x9B, 0x75, 0xDA, 0xE3, 0x13, 0xDA, 0x8C, 0xF4, 0xF7, 0x28, 0x14, 0xFC, 0x14, 0x3F, - 0x8F, 0x77, 0x79, 0xC6, 0xEB, 0x9F, 0x7F, 0xA1, 0x72, 0x99, 0xAE, 0xAD, 0xB6, 0x88, 0x90, 0x18, - 0x50, 0x1D, 0x28, 0x9E, 0x49, 0x00, 0xF7, 0xE4, 0x33, 0x1B, 0x99, 0xDE, 0xC4, 0xB5, 0x43, 0x3A, - 0xC7, 0xD3, 0x29, 0xEE, 0xB6, 0xDD, 0x26, 0x54, 0x5E, 0x96, 0xE5, 0x5B, 0x87, 0x4B, 0xE9, 0x09, }, - }; - - struct sha512 s; - size_t i; - - for (i = 0; i < sizeof(test_datas) / sizeof(*test_datas); i++) { - sha512_init(&s); - sha512_update(&s, test_datas[i], strlen(test_datas[i])); - sha512_finish(&s); - - if (memcmp(s.h, test_hashes[i], sizeof(s.h))) - fprintf(stderr, "%s: hash %zu mismatch\n", __FUNCTION__, i); - } -} - -void test_hmac_sha512(void) -{ - const char *test_datas[] = { - "Sample message for keylen=blocklen", - "Sample message for keylen<blocklen", - "Sample message for keylen=blocklen", - "Sample message for keylen<blocklen, with truncated tag" - }; - - uint8_t keybuf[256]; - const size_t keylens[] = { - 128, 64, 200, 49 - }; - - const size_t taglens[] = { - 64, 64, 64, 32 - }; - - const uint8_t test_tags[][SHA512_HASHSIZE] = { - { 0xFC, 0x25, 0xE2, 0x40, 0x65, 0x8C, 0xA7, 0x85, 0xB7, 0xA8, 0x11, 0xA8, 0xD3, 0xF7, 0xB4, 0xCA, 0x48, 0xCF, 0xA2, 0x6A, 0x8A, 0x36, 0x6B, 0xF2, 0xCD, 0x1F, 0x83, 0x6B, 0x05, 0xFC, 0xB0, 0x24, 0xBD, 0x36, 0x85, 0x30, 0x81, 0x81, 0x1D, 0x6C, 0xEA, 0x42, 0x16, 0xEB, 0xAD, 0x79, 0xDA, 0x1C, 0xFC, 0xB9, 0x5E, 0xA4, 0x58, 0x6B, 0x8A, 0x0C, 0xE3, 0x56, 0x59, 0x6A, 0x55, 0xFB, 0x13, 0x47, }, - { 0xFD, 0x44, 0xC1, 0x8B, 0xDA, 0x0B, 0xB0, 0xA6, 0xCE, 0x0E, 0x82, 0xB0, 0x31, 0xBF, 0x28, 0x18, 0xF6, 0x53, 0x9B, 0xD5, 0x6E, 0xC0, 0x0B, 0xDC, 0x10, 0xA8, 0xA2, 0xD7, 0x30, 0xB3, 0x63, 0x4D, 0xE2, 0x54, 0x5D, 0x63, 0x9B, 0x0F, 0x2C, 0xF7, 0x10, 0xD0, 0x69, 0x2C, 0x72, 0xA1, 0x89, 0x6F, 0x1F, 0x21, 0x1C, 0x2B, 0x92, 0x2D, 0x1A, 0x96, 0xC3, 0x92, 0xE0, 0x7E, 0x7E, 0xA9, 0xFE, 0xDC, }, - { 0xD9, 0x3E, 0xC8, 0xD2, 0xDE, 0x1A, 0xD2, 0xA9, 0x95, 0x7C, 0xB9, 0xB8, 0x3F, 0x14, 0xE7, 0x6A, 0xD6, 0xB5, 0xE0, 0xCC, 0xE2, 0x85, 0x07, 0x9A, 0x12, 0x7D, 0x3B, 0x14, 0xBC, 0xCB, 0x7A, 0xA7, 0x28, 0x6D, 0x4A, 0xC0, 0xD4, 0xCE, 0x64, 0x21, 0x5F, 0x2B, 0xC9, 0xE6, 0x87, 0x0B, 0x33, 0xD9, 0x74, 0x38, 0xBE, 0x4A, 0xAA, 0x20, 0xCD, 0xA5, 0xC5, 0xA9, 0x12, 0xB4, 0x8B, 0x8E, 0x27, 0xF3, }, - { 0x00, 0xF3, 0xE9, 0xA7, 0x7B, 0xB0, 0xF0, 0x6D, 0xE1, 0x5F, 0x16, 0x06, 0x03, 0xE4, 0x2B, 0x50, 0x28, 0x75, 0x88, 0x08, 0x59, 0x66, 0x64, 0xC0, 0x3E, 0x1A, 0xB8, 0xFB, 0x2B, 0x07, 0x67, 0x78, }, - }; - - size_t i; - - for (i = 0; i < sizeof(keybuf); i++) - keybuf[i] = i; - - for (i = 0; i < sizeof(test_datas) / sizeof(*test_datas); i++) { - uint8_t hmacbuf[SHA512_HASHSIZE]; - 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); - hexdump(stderr, hmacbuf, taglens[i]); - fprintf(stderr, "\n, expected:\n\t"); - hexdump(stderr, test_tags[i], taglens[i]); - fprintf(stderr, "\n"); - } - } -} - -void test_totp_sha1(void) -{ - /* Test vectors from RFC 6238 appendix B */ - const char *key = "12345678901234567890"; - const uint8_t period = 30; - const time_t t0 = 0; - - const time_t times[] = { - 59, 1111111109, 1111111111, 1234567890, 2000000000, 20000000000 - }; - const uint32_t totps[] = { - 94287082, /*0*/7081804, 14050471, 89005924, 69279037, 65353130 - }; - const uint32_t modulo = 100000000; - size_t i; - - for (i = 0; i < sizeof(times) / sizeof(*times); i++) { - uint32_t token = totp(key, strlen(key), times[i], period, t0, sha1_hmac, SHA1_HASHSIZE); - - if (token % modulo != totps[i]) - fprintf(stderr, "%s: token %zu mismatch, got %08" PRIu32 ", expected %08" PRIu32 "\n", - __FUNCTION__, i, token % modulo, totps[i]); - } -} - -void test_totp_sha256(void) -{ - /* Test vectors from RFC 6238 appendix B but key/seed from appendix A */ - const char *key = "12345678901234567890123456789012"; - const uint8_t period = 30; - const time_t t0 = 0; - - const time_t times[] = { - 59, 1111111109, 1111111111, 1234567890, 2000000000, 20000000000 - }; - const uint32_t totps[] = { - 46119246, 68084774, 67062674, 91819424, 90698825, 77737706 - }; - const uint32_t modulo = 100000000; - size_t i; - - for (i = 0; i < sizeof(times) / sizeof(*times); i++) { - uint32_t token = totp(key, strlen(key), times[i], period, t0, sha256_hmac, SHA256_HASHSIZE); - - if (token % modulo != totps[i]) - fprintf(stderr, "%s: token %zu mismatch, got %08" PRIu32 ", expected %08" PRIu32 "\n", - __FUNCTION__, i, token % modulo, totps[i]); - } -} - -void test_totp_sha512(void) -{ - /* Test vectors from RFC 6238 appendix B but key/seed from appendix A */ - const char *key = "1234567890123456789012345678901234567890123456789012345678901234"; - const uint8_t period = 30; - const time_t t0 = 0; - - const time_t times[] = { - 59, 1111111109, 1111111111, 1234567890, 2000000000, 20000000000 - }; - const uint32_t totps[] = { - 90693936, 25091201, 99943326, 93441116, 38618901, 47863826 - }; - const uint32_t modulo = 100000000; - size_t i; - - for (i = 0; i < sizeof(times) / sizeof(*times); i++) { - uint32_t token = totp(key, strlen(key), times[i], period, t0, sha512_hmac, SHA512_HASHSIZE); - - if (token % modulo != totps[i]) - fprintf(stderr, "%s: token %zu mismatch, got %08" PRIu32 ", expected %08" PRIu32 "\n", - __FUNCTION__, i, token % modulo, totps[i]); - } -} - -void test_debase32(void) -{ - const char *base32s[] = { - "MFRGG", - "MFRGGZDFMZTWQ2LKNM", - "MFRGGZDF", - }; - - const char *plaintext[] = { - "abc", - "abcdefghijk", - "abcde" - }; - - size_t i; - - for (i = 0; i < sizeof(base32s) / sizeof(*base32s); i++) { - char buffer[64]; - int len = sprintf(buffer, "%s", base32s[i]); - buffer[debase32(buffer, len)] = '\0'; - - if (strcmp(buffer, plaintext[i])) - fprintf(stderr, "%s: plaintext mismatch, got %s, expected %s\n", - __FUNCTION__, buffer, plaintext[i]); - } -} - -int main(int argc, char **argv) -{ - (void)argc; - (void)argv; - - test_sha1(); - test_sha224(); - test_sha256(); - test_sha384(); - test_sha512(); - - test_hmac_sha1(); - test_hmac_sha256(); - test_hmac_sha512(); - - test_totp_sha1(); - test_totp_sha256(); - test_totp_sha512(); - - test_debase32(); -} diff --git a/test.sh b/test.sh @@ -0,0 +1,67 @@ +#!/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/token.c b/token.c @@ -33,12 +33,11 @@ static inline uint8_t dehex(const uint8_t *s) static struct bytes uridecode(struct bytes data, bool getarg) { uint8_t *w = data.data; - uint8_t *end = w + data.len; const uint8_t *r = w; - while (r < end) { + while (r < data.end) { if (*r == '%') { - if (r + 2 >= end) + if (r + 2 >= data.end) return (struct bytes){ 0 }; *w++ = dehex(++r); if (!w[-1]) @@ -51,7 +50,7 @@ static struct bytes uridecode(struct bytes data, bool getarg) *w++ = *r++; } - return (struct bytes){ data.data, w - data.data }; + return (struct bytes){ data.data, w }; } @@ -93,14 +92,14 @@ struct token token_parse_uri(char *data) { rv.desc = uridecode(bytesnc(str, i - str), false); - if ((v = memchr(rv.desc.data, ':', rv.desc.len))) { - rv.issuer = (struct bytes){ rv.desc.data, pdiff(rv.desc.data, v++) }; + if ((v = memchr(rv.desc.data, ':', bytes_len(rv.desc)))) { + rv.issuer = bytesec(rv.desc.data, v++); rv.desc = bytesnc(v, i - v); } while (*i++) { if ((v = if_prefix(i, "secret="))) { - if (rv.key.len) + if (bytes_len(rv.key)) croak("Multiple secrets in URI"); i = v + strcspn(v, "&"); rv.key = debase32(bytesnc(v, i - v)); @@ -113,8 +112,7 @@ struct token token_parse_uri(char *data) { } else if ((v = if_prefix(i, "issuer="))) { i = v + strcspn(v, "&"); struct bytes newiss = uridecode(bytesnc(v, i - v), true); - if (rv.issuer.len && (newiss.len != rv.issuer.len || - !bytesequal(rv.issuer, newiss))) { + if (bytes_len(rv.issuer) && !bytesequal(rv.issuer, newiss)) { errno = EINVAL; return rv; } @@ -132,7 +130,7 @@ struct token token_parse_uri(char *data) { } } - if (rv.key.len && rv.desc.len) + if (bytes_len(rv.key) && bytes_len(rv.desc)) rv.valid = true; else errno = EINVAL; diff --git a/util.c b/util.c @@ -155,7 +155,7 @@ struct bytes debase32(struct bytes data) ['5'] = 30, ['6'] = 31, ['7'] = 32 }; - for (rp = data.data; rp < data.data + data.len && *rp && *rp != '='; rp++) { + for (rp = data.data; rp < data.end && *rp && *rp != '='; rp++) { uint8_t c = val[*rp]; if (!c) return (struct bytes){ 0 }; @@ -167,7 +167,7 @@ struct bytes debase32(struct bytes data) } } - return (struct bytes){ data.data, wp - data.data }; + return (struct bytes){ data.data, wp }; } void randmem(void *mem, size_t n) diff --git a/util.h b/util.h @@ -9,7 +9,7 @@ struct bytes { uint8_t *data; - size_t len; + uint8_t *end; }; typedef void (*digest_init)(void *c); @@ -81,12 +81,12 @@ static inline void *mempush(void *dst, const void *src, size_t n) static inline void *mempushb(void *dst, struct bytes data) { - return mempush(dst, data.data, data.len); + return mempush(dst, data.data, data.end - data.data); } static inline int bytesequal(struct bytes a, struct bytes b) { - return a.len == b.len && !memcmp(a.data, b.data, a.len); + return a.end - a.data == b.end - b.data && !memcmp(a.data, b.data, a.end - a.data); } static inline char *if_prefix(const char *s, const char *prefix) @@ -104,9 +104,18 @@ static inline ptrdiff_t pdiff(const void *a, const void *b) { return (const char *)b - (const char *)a; } -static inline struct bytes bytesnc(char *data, size_t len) +static inline struct bytes bytesec(void *data, void *end) { - return (struct bytes){ (void *)data, len }; + return (struct bytes){ data, end }; +} + +static inline struct bytes bytesnc(void *data, size_t n) +{ + return (struct bytes){ data, (uint8_t *)data + n }; +} + +static inline ptrdiff_t bytes_len(struct bytes bytes) { + return bytes.end - bytes.data; } #endif