diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/base32.c | 93 | ||||
| -rw-r--r-- | src/base32.h | 10 | ||||
| -rw-r--r-- | src/common.h | 27 | ||||
| -rw-r--r-- | src/hmac.c | 41 | ||||
| -rw-r--r-- | src/hmac.h | 11 | ||||
| -rw-r--r-- | src/main.c | 191 | ||||
| -rw-r--r-- | src/sha1-generic.c | 78 | ||||
| -rw-r--r-- | src/sha1-x64.c | 98 | ||||
| -rw-r--r-- | src/sha1.c | 76 | ||||
| -rw-r--r-- | src/sha1.h | 21 | ||||
| -rw-r--r-- | src/xendian.h | 21 | 
11 files changed, 667 insertions, 0 deletions
diff --git a/src/base32.c b/src/base32.c new file mode 100644 index 0000000..82c5b48 --- /dev/null +++ b/src/base32.c @@ -0,0 +1,93 @@ +#include <assert.h> + +#include "base32.h" +#include "common.h" + +static inline bool b32blktoa(uint8_t *restrict, const uint8_t *restrict) +	__attribute__((always_inline)); + +static const uint8_t lookup[] = { +	/* [00…07] = */ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +	/* [08…0F] = */ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +	/* [10…17] = */ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +	/* [18…1F] = */ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +	/* [20…27] = */ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +	/* [28…2F] = */ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +	/* [30…37] = */ 0xFF, 0xFF,   26,   27,   28,   29,   30,   31, +	/* [38…3F] = */ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,    0, 0xFF, 0xFF, +	/* [40…47] = */ 0xFF,    0,    1,    2,    3,    4,    5,    6, +	/* [48…4F] = */    7,    8,    9,   10,   11,   12,   13,   14, +	/* [50…57] = */   15,   16,   17,   18,   19,   20,   21,   22, +	/* [58…5F] = */   23,   24,  25,  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +	/* [60…67] = */ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +	/* [68…6F] = */ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +	/* [70…77] = */ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +	/* [78…7F] = */ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +	/* [80…87] = */ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +	/* [88…8F] = */ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +	/* [90…97] = */ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +	/* [98…9F] = */ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +	/* [A0…A7] = */ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +	/* [A8…AF] = */ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +	/* [B0…B7] = */ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +	/* [B8…BF] = */ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +	/* [C0…C7] = */ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +	/* [C8…CF] = */ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +	/* [D0…D7] = */ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +	/* [D8…DF] = */ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +	/* [E0…E7] = */ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +	/* [E8…EF] = */ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +	/* [F0…F7] = */ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +	/* [F8…FF] = */ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +}; + +bool +b32toa(uint8_t *restrict dst, const char *restrict src, size_t len) +{ +	assert(len != 0); + +	size_t i, j; +	for (i = j = 0; len - i >= 8; i += 8, j += 5) { +		uint8_t bits[] = { +			lookup[(uint8_t)src[i + 0]], +			lookup[(uint8_t)src[i + 1]], +			lookup[(uint8_t)src[i + 2]], +			lookup[(uint8_t)src[i + 3]], +			lookup[(uint8_t)src[i + 4]], +			lookup[(uint8_t)src[i + 5]], +			lookup[(uint8_t)src[i + 6]], +			lookup[(uint8_t)src[i + 7]], +		}; +		if (!b32blktoa(dst + j, bits)) +			return false; +	} + +	uint8_t bits[8] = {0}; +	switch (len - i) { +	case 7: bits[6] = lookup[(uint8_t)src[i + 6]]; /* fallthrough */ +	case 6: bits[5] = lookup[(uint8_t)src[i + 5]]; /* fallthrough */ +	case 5: bits[4] = lookup[(uint8_t)src[i + 4]]; /* fallthrough */ +	case 4: bits[3] = lookup[(uint8_t)src[i + 3]]; /* fallthrough */ +	case 3: bits[2] = lookup[(uint8_t)src[i + 2]]; /* fallthrough */ +	case 2: bits[1] = lookup[(uint8_t)src[i + 1]]; /* fallthrough */ +	case 1: bits[0] = lookup[(uint8_t)src[i + 0]]; +		return b32blktoa(dst + j, bits); +	} +	return true; +} + +bool +b32blktoa(uint8_t *restrict dst, const uint8_t *restrict src) +{ +	uint8_t or = src[0] | src[1] | src[2] | src[3] +	           | src[4] | src[5] | src[6] | src[7]; +	if (or == 0xFF) +		return false; + +	dst[0] = src[0]<<3 | src[1]>>2; +	dst[1] = src[1]<<6 | src[2]<<1 | src[3]>>4; +	dst[2] = src[3]<<4 | src[4]>>1; +	dst[3] = src[4]<<7 | src[5]<<2 | src[6]>>3; +	dst[4] = src[6]<<5 | src[7]>>0; +	return true; +} diff --git a/src/base32.h b/src/base32.h new file mode 100644 index 0000000..2581878 --- /dev/null +++ b/src/base32.h @@ -0,0 +1,10 @@ +#ifndef TOTP_BASE32_H +#define TOTP_BASE32_H + +#include <stdbool.h> +#include <stddef.h> +#include <stdint.h> + +bool b32toa(uint8_t *restrict, const char *restrict, size_t); + +#endif /* !TOTP_BASE32_H */ diff --git a/src/common.h b/src/common.h new file mode 100644 index 0000000..c1d21c8 --- /dev/null +++ b/src/common.h @@ -0,0 +1,27 @@ +#ifndef TOTP_COMMON_H +#define TOTP_COMMON_H + +#if !__GNUC__ +#	define __attribute__(x) +#endif + +/* TODO: Is this endian stuff potentially useful? */ + +/* If C23 or newer include this to get byte-order macros */ +#if __STDC_VERSION__ >= 202311L +#	include <stdbit.h> +#endif + +#if (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)        \ +	|| (defined(__STDC_ENDIAN_NATIVE__)                                        \ +        && __STDC_ENDIAN_NATIVE__ == __STDC_ENDIAN_BIG__) +#	define ENDIAN_BIG 1 +#elif (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)   \ +	|| (defined(__STDC_ENDIAN_NATIVE__)                                        \ +        && __STDC_ENDIAN_NATIVE__ == __STDC_ENDIAN_LITTLE__) +#	define ENDIAN_LITTLE 1 +#else +#	define ENDIAN_UNKNOWN 1 +#endif + +#endif /* !TOTP_COMMON_H */ diff --git a/src/hmac.c b/src/hmac.c new file mode 100644 index 0000000..5175ee2 --- /dev/null +++ b/src/hmac.c @@ -0,0 +1,41 @@ +#include <string.h> + +#include "sha1.h" + +#define IPAD (0x36) +#define OPAD (0x5C) + +void +hmac_sha1(uint8_t *restrict out, +          const uint8_t *restrict key, size_t keysz, +          const uint8_t *restrict msg, size_t msgsz) +{ +	uint8_t keyext[SHA1BLKSZ] = {0}, +	        keyipad[SHA1BLKSZ], +			keyopad[SHA1BLKSZ]; + +	if (keysz > SHA1BLKSZ) { +		sha1_t sha; +		sha1init(&sha); +		sha1hash(&sha, key, keysz); +		sha1end(&sha, keyext); +	} else +		memcpy(keyext, key, keysz); + +	for (size_t i = 0; i < sizeof(keyext); i++) { +		keyipad[i] = keyext[i] ^ IPAD; +		keyopad[i] = keyext[i] ^ OPAD; +	} + +	sha1_t sha; +	uint8_t dgst[SHA1DGSTSZ]; +	sha1init(&sha); +	sha1hash(&sha, keyipad, sizeof(keyipad)); +	sha1hash(&sha, msg, msgsz); +	sha1end(&sha, dgst); + +	sha1init(&sha); +	sha1hash(&sha, keyopad, sizeof(keyopad)); +	sha1hash(&sha, dgst, sizeof(dgst)); +	sha1end(&sha, out); +} diff --git a/src/hmac.h b/src/hmac.h new file mode 100644 index 0000000..3c3e8e7 --- /dev/null +++ b/src/hmac.h @@ -0,0 +1,11 @@ +#ifndef TOTP_HMAC_H +#define TOTP_HMAC_H + +#include <stddef.h> +#include <stdint.h> + +void hmac_sha1(uint8_t *restrict, +               const uint8_t *restrict, size_t, +               const uint8_t *restrict, size_t); + +#endif /* !TOTP_HMAC_H */ diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..40318cf --- /dev/null +++ b/src/main.c @@ -0,0 +1,191 @@ +#include <err.h> +#include <errno.h> +#include <getopt.h> +#include <inttypes.h> +#include <libgen.h> +#include <limits.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdnoreturn.h> +#include <string.h> +#include <time.h> + +#include "base32.h" +#include "common.h" +#include "hmac.h" +#include "sha1.h" +#include "xendian.h" + +static void process(const char *, size_t); +static void process_stdin(void); +static inline uint32_t pow32(uint32_t, uint32_t) +	__attribute__((always_inline, const)); +static inline bool xisdigit(char) +	__attribute__((always_inline, const)); +static inline bool bigendian(void) +	__attribute__((always_inline, const)); + +static int digits = 6, period = 30; + +static noreturn void +usage(const char *argv0) +{ +	fprintf(stderr, +		"Usage: %s [-d digits] [-p period] [secret]\n" +		"       %s -h\n", +		argv0, argv0); +	exit(EXIT_FAILURE); +} + +int +main(int argc, char **argv) +{ +	int opt; +	static const struct option longopts[] = { +		{"digits", required_argument, 0, 'd'}, +		{"period", required_argument, 0, 'p'}, +		{0}, +	}; + +	argv[0] = basename(argv[0]); +	while ((opt = getopt_long(argc, argv, "d:p:", longopts, NULL)) != -1) { +		switch (opt) { +		case 'd': +		case 'p': { +			/* strtol() allows for numbers with leading spaces and a +			   ‘+’/‘-’.  We don’t want that, so assert that the input +			   begins with a number. */ +			if (!xisdigit(optarg[0])) +				errx(1, "%s: Invalid integer", optarg); + +			errno = 0; +			char *endptr; +			long n = strtol(optarg, &endptr, 10); + +			/* There are trailing invalid digits */ +			if (*endptr != 0) +				errx(1, "%s: Invalid integer", optarg); + +			/* The number was too large.  We asserted that the input +			   didn’t start with ‘-’ so we can ignore checking for +			   LONG_MIN. */ +			if (n > INT_MAX) +				errno = ERANGE; +			if (errno == ERANGE) +				err(1, "%s", optarg); + +			if (n == 0) +				errx(1, "%s: Integer must be non-zero", optarg); +			if (opt == 'd') +				digits = (int)n; +			else +				period = (int)n; +			break; +		} +		default: +			usage(argv[0]); +		} +	} + +	argc -= optind; +	argv += optind; + +	switch (argc) { +	case 0: +		process_stdin(); +		break; +	case 1: +		process(argv[0], strlen(argv[0])); +		break; +	default: +		usage(argv[-optind]); +	} + +	return EXIT_SUCCESS; +} + +void +process_stdin(void) +{ +	ssize_t nr; +	size_t len; +	char *line = NULL; +	while ((nr = getline(&line, &len, stdin)) != -1) { +		if (line[nr - 1] == '\n') +			line[--nr] = 0; +		process(line, nr); +	} +	if (errno != 0) +		err(1, "getline"); +} + +void +process(const char *s, size_t n) +{ +	/* Remove padding bytes */ +	while (n > 0 && s[n - 1] == '=') +		n--; +	if (n == 0) +		errx(1, "Empty Base32 input"); + +	static uint8_t _key[256]; +	uint8_t *key = _key; + +	size_t keysz = n * 5 / 8; +	if (keysz > sizeof(_key)) { +		if ((key = malloc(keysz)) == NULL) +			err(1, "malloc"); +	} + +	if (!b32toa(key, s, n)) +		errx(1, "%s: Invalid Base32 input", s); + +	/* time(2) claims that this call will never fail if passed a NULL +	   argument.  We cast the time_t to uint64_t which will always be +	   safe to do. */ +	uint64_t epoch = htobe64((uint64_t)time(NULL) / (uint64_t)period); +	uint8_t dgst[SHA1DGSTSZ]; +	hmac_sha1(dgst, key, keysz, (uint8_t *)&epoch, sizeof(epoch)); + +	int off = dgst[19] & 0x0F; +	uint32_t binc = (dgst[off + 0] & 0x7F) << 24 +                  | (dgst[off + 1] & 0xFF) << 16 +                  | (dgst[off + 2] & 0xFF) <<  8 +                  | (dgst[off + 3] & 0xFF) <<  0; +	printf("%0*" PRId32 "\n", digits, binc % pow32(10, digits)); + +	if (key != _key) +		free(key); +} + +/* TODO: Check for overflow? */ +uint32_t +pow32(uint32_t x, uint32_t y) +{ +	uint32_t n = x; +	if (y == 0) +		return 1; +	while (--y != 0) +		x *= n; +	return x; +} + +bool +xisdigit(char ch) +{ +	return ch >= '0' && ch <= '9'; +} + +bool +bigendian(void) +{ +	union { +		uint16_t u16; +		uint8_t  u8[2]; +	} u = { +		.u16 = 0x0102, +	}; +	u.u16 = 0x0102U; +	return u.u8[0] == 1; +} diff --git a/src/sha1-generic.c b/src/sha1-generic.c new file mode 100644 index 0000000..d897a8f --- /dev/null +++ b/src/sha1-generic.c @@ -0,0 +1,78 @@ +#include "common.h" +#include "sha1.h" +#include "xendian.h" + +static inline uint32_t rotl32(uint32_t x, uint8_t bits) +	__attribute__((always_inline, const)); + +static const uint32_t K[] = { +	0x5A827999, +	0x6ED9EBA1, +	0x8F1BBCDC, +	0xCA62C1D6, +}; + +void +sha1hashblk(sha1_t *s, const uint8_t *blk) +{ +	uint32_t w[80]; +	uint32_t a, b, c, d, e, tmp; + +	for (int i = 0; i < 16; i++) +		w[i] = htobe32(((uint32_t *)blk)[i]); +	for (int i = 16; i < 32; i++) +		w[i] = rotl32(w[i-3] ^ w[i-8] ^ w[i-14] ^ w[i-16], 1); +	for (int i = 32; i < 80; i++) +		w[i] = rotl32(w[i-6] ^ w[i-16] ^ w[i-28] ^ w[i-32], 2); + +	a = s->dgst[0]; +	b = s->dgst[1]; +	c = s->dgst[2]; +	d = s->dgst[3]; +	e = s->dgst[4]; + +	for (int i = 0; i < 80; i++) { +		uint32_t f, k; + +		if (i < 20) { +			f = b&c | ~b&d; +			k = K[0]; +		} else if (i < 40) { +			f = b ^ c ^ d; +			k = K[1]; +		} else if (i < 60) { +			f = b&c | b&d | c&d; +			k = K[2]; +		} else { +			f = b ^ c ^ d; +			k = K[3]; +		} + +		tmp = rotl32(a, 5) + f + e + w[i] + k; +		e = d; +		d = c; +		c = rotl32(b, 30); +		b = a; +		a = tmp; +	} + +	s->dgst[0] += a; +	s->dgst[1] += b; +	s->dgst[2] += c; +	s->dgst[3] += d; +	s->dgst[4] += e; +} + +uint32_t +rotl32(uint32_t x, uint8_t bits) +{ +#if (__GNUC__ || __TINYC__) && __x86_64__ +	__asm__ ("roll %1, %0" : "+r" (x) : "c" (bits) : "cc"); +	return x; +#elif __GNUC__ && __aarch64__ /* TODO: Test this! */ +	__asm__ ("ror %0, %0, %1" : "+r" (x) : "c" (-bits)); +	return x; +#else +	return (x << bits) | (x >> (32 - bits)); +#endif +} diff --git a/src/sha1-x64.c b/src/sha1-x64.c new file mode 100644 index 0000000..be19ab7 --- /dev/null +++ b/src/sha1-x64.c @@ -0,0 +1,98 @@ +#include <immintrin.h> + +#include "sha1.h" + +#define R(mi, mj, mk, ml, ei, ej, f)                                           \ +	do {                                                                       \ +		ei = _mm_sha1nexte_epu32(ei, mi);                                      \ +		ej = abcd;                                                             \ +		mj = _mm_sha1msg2_epu32(mj, mi);                                       \ +		abcd = _mm_sha1rnds4_epu32(abcd, ei, f);                               \ +		ml = _mm_sha1msg1_epu32(ml, mi);                                       \ +		mk = _mm_xor_si128(mk, mi);                                            \ +	} while (0) + +void +sha1hashblk(sha1_t *s, const uint8_t *blk) +{ +	__m128i abcd, e0, e1; +	__m128i abcd_save, e_save; +	__m128i msg0, msg1, msg2, msg3; + +	/* Masks for swapping endianness.  We make BSWAPDMSK a macro to +	   please the compiler (it wants immediate values). */ +#define bswapdmsk 0x1B  /* 0b00'01'10'11 */ +	const __m128i bswapbmsk = _mm_set_epi64x( +		0x0001020304050607ULL, +		0x08090a0b0c0d0e0fULL +	); + +	const __m128i *blkx = (const __m128i *)blk; + +	abcd = _mm_shuffle_epi32(_mm_loadu_si128((__m128i *)s->dgst), bswapdmsk); +	e0 = _mm_set_epi32(s->dgst[4], 0, 0, 0); + +	abcd_save = abcd; +	e_save = e0; + +	/* Rounds 0–3 */ +	msg0 = _mm_shuffle_epi8(_mm_loadu_si128(blkx + 0), bswapbmsk); +	e0 = _mm_add_epi32(e0, msg0); +	e1 = abcd; +	abcd = _mm_sha1rnds4_epu32(abcd, e0, 0); + +	/* Rounds 4–7 */ +	msg1 = _mm_shuffle_epi8(_mm_loadu_si128(blkx + 1), bswapbmsk); +	e1 = _mm_sha1nexte_epu32(e1, msg1); +	e0 = abcd; +	abcd = _mm_sha1rnds4_epu32(abcd, e1, 0); +	msg0 = _mm_sha1msg1_epu32(msg0, msg1); + +	/* Rounds 8–11 */ +	msg2 = _mm_shuffle_epi8(_mm_loadu_si128(blkx + 2), bswapbmsk); +	e0 = _mm_sha1nexte_epu32(e0, msg2); +	e1 = abcd; +	abcd = _mm_sha1rnds4_epu32(abcd, e0, 0); +	msg1 = _mm_sha1msg1_epu32(msg1, msg2); +	msg0 = _mm_xor_si128(msg0, msg2); + +	msg3 = _mm_shuffle_epi8(_mm_loadu_si128(blkx + 3), bswapbmsk); +	R(msg3, msg0, msg1, msg2, e1, e0, 0); /* Rounds 12–15 */ +	R(msg0, msg1, msg2, msg3, e0, e1, 0); /* Rounds 16–19 */ +	R(msg1, msg2, msg3, msg0, e1, e0, 1); /* Rounds 20–23 */ +	R(msg2, msg3, msg0, msg1, e0, e1, 1); /* Rounds 24–27 */ +	R(msg3, msg0, msg1, msg2, e1, e0, 1); /* Rounds 28–31 */ +	R(msg0, msg1, msg2, msg3, e0, e1, 1); /* Rounds 32–35 */ +	R(msg1, msg2, msg3, msg0, e1, e0, 1); /* Rounds 36–39 */ +	R(msg2, msg3, msg0, msg1, e0, e1, 2); /* Rounds 40–43 */ +	R(msg3, msg0, msg1, msg2, e1, e0, 2); /* Rounds 44–47 */ +	R(msg0, msg1, msg2, msg3, e0, e1, 2); /* Rounds 48–51 */ +	R(msg1, msg2, msg3, msg0, e1, e0, 2); /* Rounds 52–55 */ +	R(msg2, msg3, msg0, msg1, e0, e1, 2); /* Rounds 56–59 */ +	R(msg3, msg0, msg1, msg2, e1, e0, 3); /* Rounds 60–63 */ +	R(msg0, msg1, msg2, msg3, e0, e1, 3); /* Rounds 64–67 */ + +	/* Rounds 68–71 */ +	e1 = _mm_sha1nexte_epu32(e1, msg1); +	e0 = abcd; +	msg2 = _mm_sha1msg2_epu32(msg2, msg1); +	abcd = _mm_sha1rnds4_epu32(abcd, e1, 3); +	msg3 = _mm_xor_si128(msg3, msg1); + +	/* Rounds 72–75 */ +	e0 = _mm_sha1nexte_epu32(e0, msg2); +	e1 = abcd; +	msg3 = _mm_sha1msg2_epu32(msg3, msg2); +	abcd = _mm_sha1rnds4_epu32(abcd, e0, 3); + +	/* Rounds 76–79 */ +	e1 = _mm_sha1nexte_epu32(e1, msg3); +	e0 = abcd; +	abcd = _mm_sha1rnds4_epu32(abcd, e1, 3); + +	e0 = _mm_sha1nexte_epu32(e0, e_save); +	abcd = _mm_add_epi32(abcd, abcd_save); + +	_mm_storeu_si128((__m128i *)s->dgst, _mm_shuffle_epi32(abcd, bswapdmsk)); +	s->dgst[4] = _mm_extract_epi32(e0, 3); +} diff --git a/src/sha1.c b/src/sha1.c new file mode 100644 index 0000000..5759991 --- /dev/null +++ b/src/sha1.c @@ -0,0 +1,76 @@ +#include <err.h> +#include <errno.h> +#include <string.h> + +#include "sha1.h" +#include "xendian.h" + +#define lengthof(x) (sizeof(x) / sizeof(*(x))) +#define MIN(x, y)   ((x) < (y) ? (x) : (y)) + +void sha1hashblk(sha1_t *, const uint8_t *); + +void +sha1init(sha1_t *s) +{ +	static const uint32_t H[] = { +		0x67452301, +		0xEFCDAB89, +		0x98BADCFE, +		0x10325476, +		0xC3D2E1F0, +	}; +	memcpy(s->dgst, H, sizeof(H)); +	s->msgsz = s->bufsz = 0; +} + +void +sha1hash(sha1_t *s, const uint8_t *msg, size_t msgsz) +{ +	if (s->msgsz + (msgsz * 8) < s->msgsz) { +		errno = EOVERFLOW; +		err(1, "sha1"); +	} + +	s->msgsz += msgsz * 8; + +	while (msgsz != 0) { +		size_t free_space = SHA1BLKSZ - s->bufsz; +		size_t ncpy = MIN(msgsz, free_space); +		memcpy(s->buf + s->bufsz, msg, ncpy); +		s->bufsz += ncpy; +		msg += ncpy; +		msgsz -= ncpy; + +		if (s->bufsz == SHA1BLKSZ) { +			sha1hashblk(s, s->buf); +			s->bufsz = 0; +		} +	} +} + +void +sha1end(sha1_t *s, uint8_t *dgst) +{ +	s->buf[s->bufsz++] = 0x80; + +	if (s->bufsz > SHA1BLKSZ - sizeof(uint64_t)) { +		while (s->bufsz < SHA1BLKSZ) +			s->buf[s->bufsz++] = 0; +		sha1hashblk(s, s->buf); +		s->bufsz = 0; +	} + +	while (s->bufsz < 56) +		s->buf[s->bufsz++] = 0; +	uint64_t n = htobe64(s->msgsz); +	memcpy(s->buf + (SHA1BLKSZ/8 - 1)*sizeof(uint64_t), &n, sizeof(n)); + +	sha1hashblk(s, s->buf); + +	for (size_t i = 0; i < lengthof(s->dgst); i++) { +		/* Pretty please compiler optimize this */ +		uint32_t n = htobe32(s->dgst[i]); +		memcpy(dgst + i*sizeof(uint32_t), &n, sizeof(n)); +	} +} diff --git a/src/sha1.h b/src/sha1.h new file mode 100644 index 0000000..ea08d37 --- /dev/null +++ b/src/sha1.h @@ -0,0 +1,21 @@ +#ifndef TOTP_SHA1_H +#define TOTP_SHA1_H + +#include <stddef.h> +#include <stdint.h> + +#define SHA1DGSTSZ (20) +#define SHA1BLKSZ  (64) + +typedef struct { +	uint32_t dgst[SHA1DGSTSZ / sizeof(uint32_t)]; +	uint64_t msgsz; +	uint8_t buf[SHA1BLKSZ]; +	size_t bufsz; +} sha1_t; + +void sha1init(sha1_t *); +void sha1hash(sha1_t *, const uint8_t *, size_t); +void sha1end(sha1_t *, uint8_t *); + +#endif /* !TOTP_SHA1_H */ diff --git a/src/xendian.h b/src/xendian.h new file mode 100644 index 0000000..b43661f --- /dev/null +++ b/src/xendian.h @@ -0,0 +1,21 @@ +#ifndef TOTP_XENDIAN_H +#define TOTP_XENDIAN_H + +/* This header grabs the htobe64() and co. functions in a more +   cross-platform manner.  In general you will find these functions in +   <sys/endian.h>, however Linux and OpenBSD include them in <endian.h>. +   To make things even better this header doesn’t exist on MacOS so we +   need to define wrapper macros for the htonXX() functions from +   <arpa/inet.h>. */ + +#if defined(__OpenBSD__) || defined(__linux__) +#	include <endian.h> +#elif defined(__APPLE__) +#	include <arpa/inet.h> +#	define htobe32(x) htonl(x) +#	define htobe64(x) htonll(x) +#else +#	include <sys/endian.h> +#endif + +#endif /* !TOTP_XENDIAN_H */  |