totp

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

token.c (3051B)


      1 #include <string.h>
      2 #include <stdlib.h>
      3 #include <errno.h>
      4 #include <stdio.h>
      5 #include "token.h"
      6 #include "util.h"
      7 
      8 const char *digest_names[] = {
      9 	"SHA1",
     10 	"SHA224",
     11 	"SHA256",
     12 	"SHA384",
     13 	"SHA512",
     14 };
     15 
     16 static inline uint8_t dehex(const uint8_t *s)
     17 {
     18 	static const uint8_t vals[256] = {
     19 		['0'] = 1, ['1'] = 2, ['2'] = 3, ['3'] = 4, ['4'] = 5,
     20 		['5'] = 6, ['6'] = 7, ['7'] = 8, ['8'] = 9, ['9'] = 10,
     21 		['A'] = 11, ['B'] = 12, ['C'] = 13, ['D'] = 14, ['E'] = 15,
     22 		['F'] = 16, ['a'] = 11, ['b'] = 12, ['c'] = 13, ['d'] = 14,
     23 		['e'] = 15, ['f'] = 16,
     24 	};
     25 
     26 	if (!vals[s[0]] || !vals[s[1]])
     27 		return 0;
     28 
     29 	return (vals[s[0]] - 1) << 4 |
     30 	       (vals[s[1]] - 1);
     31 }
     32 
     33 static struct bytes uridecode(struct bytes data, bool getarg)
     34 {
     35 	uint8_t *w = data.data;
     36 	const uint8_t *r = w;
     37 
     38 	while (r < data.end) {
     39 		if (*r == '%') {
     40 			if (r + 2 >= data.end)
     41 				return (struct bytes){ 0 };
     42 			*w++ = dehex(++r);
     43 			if (!w[-1])
     44 				return (struct bytes){ 0 };
     45 			r += 2;
     46 		} else if (getarg && *r == '+') {
     47 			*w++ = ' ';
     48 			r++;
     49 		} else
     50 			*w++ = *r++;
     51 	}
     52 
     53 	return (struct bytes){ data.data, w };
     54 }
     55 
     56 
     57 static uint8_t get_digest(const char *s, size_t len)
     58 {
     59 	size_t i;
     60 
     61 	for (i = 0; i < sizeof(digest_names) / sizeof(*digest_names); i++)
     62 		if (!strncmp(s, digest_names[i], len) &&
     63 		    !digest_names[i][len])
     64 			return i;
     65 	
     66 	fprintf(stderr, "Unknown digest \"%.*s\", assuming %s\n", 
     67 		(int)len, s, digest_names[DIGEST_SHA1]);
     68 	return DIGEST_SHA1;
     69 }
     70 
     71 static uint8_t strtou8(const char *s, char **end) {
     72 	unsigned long v = strtoul(s, end, 10);
     73 	if (v > UINT8_MAX || v == 0) {
     74 		errno = ERANGE;
     75 		return 0;
     76 	}
     77 	return v;
     78 }
     79 
     80 struct token token_parse_uri(char *data) {
     81 	struct token rv = { .t0 = 0, .period = 30, .digits = 6, .digest = DIGEST_SHA1, .valid = false };
     82 	char *str;
     83 	char *i;
     84 	char *v;
     85 
     86 	if (!(str = if_prefix(data, "otpauth://totp/")))
     87 		return rv;
     88 
     89 	i = strchr(str, '?');
     90 	if (!i)
     91 		return rv;
     92 
     93 	rv.desc = uridecode(bytesec(str, i), false);
     94 
     95 	if ((v = memchr(rv.desc.data, ':', bytes_len(rv.desc)))) {
     96 		rv.issuer = bytesec(rv.desc.data, v++);
     97 		rv.desc = bytesec(v, rv.desc.end);
     98 	}
     99 
    100 	while (*i++) {
    101 		if ((v = if_prefix(i, "secret="))) {
    102 			if (bytes_len(rv.key))
    103 				croak("Multiple secrets in URI");
    104 			i = v + strcspn(v, "&");
    105 			rv.key = debase32(bytesec(v, i));
    106 		} else if ((v = if_prefix(i, "digits="))) {
    107 			if (!(rv.digits = strtou8(v, &i)))
    108 				return rv;
    109 		} else if ((v = if_prefix(i, "period="))) {
    110 			if (!(rv.period = strtou8(v, &i)))
    111 				return rv;
    112 		} else if ((v = if_prefix(i, "issuer="))) {
    113 			i = v + strcspn(v, "&");
    114 			struct bytes newiss = uridecode(bytesec(v, i), true);
    115 			if (bytes_len(rv.issuer) && !bytesequal(rv.issuer, newiss)) {
    116 				errno = EINVAL;
    117 				return rv;
    118 			}
    119 			rv.issuer = newiss;
    120 		} else if ((v = if_prefix(i, "algorithm="))) {
    121 			i = v + strcspn(v, "&");
    122 			rv.digest = get_digest(v, i - v);
    123 		} else {
    124 			i += strcspn(i, "&");
    125 		}
    126 
    127 		if (!i && *i != '&') {
    128 			errno = EINVAL;
    129 			return rv;
    130 		}
    131 	}
    132 
    133 	if (bytes_len(rv.key) && bytes_len(rv.desc))
    134 		rv.valid = true;
    135 	else
    136 		errno = EINVAL;
    137 
    138 	return rv;
    139 }