totp

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

db.c (5510B)


      1 #include <stdbool.h>
      2 #include <stdint.h>
      3 
      4 #include <errno.h>
      5 #include <fcntl.h>
      6 #include <unistd.h>
      7 
      8 #include "db.h"
      9 #include "util.h"
     10 #include "token.h"
     11 #include "tiny-AES-c/aes.h"
     12 
     13 struct header {
     14 	uint8_t magic[4];
     15 	uint8_t version;
     16 };
     17 
     18 static bool verify_db_legacy(int fd, struct AES_ctx *c, uint8_t *keybuf)
     19 {
     20 	uint8_t rbuf[AES_BLOCKLEN];
     21 	int r;
     22 	size_t rused = 0;
     23 	struct header *h;
     24 
     25 	while ((r = read(fd, rbuf + rused, sizeof(rbuf) - rused)) > 0)
     26 		rused += r;
     27 
     28 	if (rused < sizeof(rbuf)) {
     29 		errno = TOTP_EEOF;
     30 		return false;
     31 	}
     32 
     33 	AES_init_ctx_iv(c, keybuf, keybuf + AES_KEYLEN);
     34 	rused = 0;
     35 
     36 	AES_CBC_decrypt_buffer(c, rbuf, sizeof(rbuf));
     37 	h = (struct header *)(rbuf + rbuf[0] % (sizeof(rbuf) - sizeof(*h) - 1) + 1);
     38 
     39 	if (h->magic[0] == 'T' &&
     40 	    h->magic[1] == 'O' &&
     41 	    h->magic[2] == 'T' &&
     42 	    h->magic[3] == 'P' &&
     43 	    h->version == 1)
     44 		return true;
     45 
     46 	errno = TOTP_EMALF;
     47 	return false;
     48 }
     49 
     50 static bool verify_db(int fd, struct AES_ctx *c, uint8_t *keybuf)
     51 {
     52 	uint8_t rbuf[AES_BLOCKLEN];
     53 	int r;
     54 	size_t rused = 0;
     55 	struct header *h;
     56 
     57 	while ((r = read(fd, rbuf + rused, sizeof(rbuf) - rused)) > 0)
     58 		rused += r;
     59 
     60 	if (rused < sizeof(rbuf)) {
     61 		errno = TOTP_EEOF;
     62 		return false;
     63 	}
     64 
     65 	AES_init_ctx_iv(c, keybuf, rbuf);
     66 	rused = 0;
     67 
     68 	while ((r = read(fd, rbuf + rused, sizeof(rbuf) - rused)) > 0)
     69 		rused += r;
     70 
     71 	if (rused < sizeof(rbuf)) {
     72 		errno = TOTP_EEOF;
     73 		return false;
     74 	}
     75 
     76 	AES_CBC_decrypt_buffer(c, rbuf, sizeof(rbuf));
     77 	h = (struct header *)(rbuf + rbuf[0] % (sizeof(rbuf) - sizeof(*h) - 1) + 1);
     78 
     79 	if (h->magic[0] == 'T' &&
     80 	    h->magic[1] == 'O' &&
     81 	    h->magic[2] == 'T' &&
     82 	    h->magic[3] == 'P' &&
     83 	    h->version == 1)
     84 		return true;
     85 
     86 	errno = TOTP_EMALF;
     87 	return false;
     88 }
     89 
     90 int db_open_read(const char *filename, struct AES_ctx *c, uint8_t *keybuf) {
     91 	int fd = open(filename, O_RDONLY);
     92 
     93 	if (fd < 0)
     94 		return fd;
     95 	
     96 	if (verify_db(fd, c, keybuf))
     97 		return fd;
     98 
     99 	if (!lseek(fd, 0, SEEK_SET) && verify_db_legacy(fd, c, keybuf))
    100 		return fd;
    101 
    102 	close(fd);
    103 	return -1;
    104 }
    105 
    106 static int write_header(int fd, struct AES_ctx *c)
    107 {
    108 	uint8_t wbuf[AES_BLOCKLEN];
    109 	int w;
    110 	uint8_t *wp = wbuf;
    111 	uint8_t * const wend = 1[&wbuf];
    112 	struct header *h;
    113 
    114 	randmem(wbuf, sizeof(wbuf));
    115 
    116 	h = (struct header *)(wbuf + wbuf[0] % (sizeof(wbuf) - sizeof(*h) - 1) + 1);
    117 
    118 	h->magic[0] = 'T';
    119 	h->magic[1] = 'O';
    120 	h->magic[2] = 'T';
    121 	h->magic[3] = 'P';
    122 	h->version = 1;
    123 
    124 	AES_CBC_encrypt_buffer(c, wbuf, sizeof(wbuf));
    125 
    126 	while (wp < wend && (w = write(fd, wp, wend - wp)) > 0)
    127 		wp += w;
    128 
    129 	if (w < 0)
    130 		return -1;
    131 
    132 	if (wp < wend) {
    133 		errno = EIO;
    134 		return -1;
    135 	}
    136 
    137 	return 0;
    138 
    139 }
    140 
    141 int write_salt(int fd, uint8_t *saltbuf)
    142 {
    143 	uint8_t *wp = saltbuf;
    144 	uint8_t * const wend = saltbuf + AES_BLOCKLEN;
    145 	ssize_t w;
    146 
    147 	while (wp < wend && (w = write(fd, wp, wend - wp)) > 0)
    148 		wp += w;
    149 
    150 	return wp < wend;
    151 }
    152 
    153 int db_open_write(const char *filename, struct AES_ctx *c, uint8_t *keybuf)
    154 {
    155 	uint8_t saltbuf[AES_BLOCKLEN];
    156 	int fd = open(filename, O_WRONLY | O_CREAT | O_EXCL, 0600);
    157 
    158 	if (fd < 0)
    159 		return fd;
    160 
    161 	randmem(saltbuf, sizeof(saltbuf));
    162 	AES_init_ctx_iv(c, keybuf, saltbuf);
    163 
    164 	if (write_salt(fd, saltbuf) ||
    165 	    write_header(fd, c)) {
    166 		int tmp_errno = errno;
    167 		(void)unlink(filename);
    168 		(void)close(fd);
    169 		errno = tmp_errno;
    170 
    171 		return -1;
    172 	}
    173 
    174 	return fd;
    175 }
    176 
    177 void db_foreach(int fd, struct AES_ctx *c,
    178 		void (*key_cb)(struct token *token,
    179 			       void *data),
    180 		void *cb_data)
    181 {
    182 	uint8_t decbuf[512];
    183 	uint8_t rbuf[AES_BLOCKLEN];
    184 	uint8_t *dp = decbuf;
    185 	uint8_t * const dend = 1[&decbuf];
    186 	uint8_t *rp = rbuf;
    187 	uint8_t * const rend = 1[&rbuf];
    188 	int r;
    189 
    190 	while ((r = read(fd, rp, rend - rp)) > 0) {
    191 		struct totpkey *kh = (struct totpkey *)decbuf;
    192 		if ((rp += r) < rend)
    193 			continue;
    194 		AES_CBC_decrypt_buffer(c, rbuf, sizeof(rbuf));
    195 		if (dp + sizeof(rbuf) >= dend)
    196 			break;
    197 		dp = mempush(dp, rp = rbuf, sizeof(rbuf));
    198 
    199 		if (decbuf + sizeof(*kh) > dp ||
    200 		    decbuf + sizeof(*kh) + kh->keylen + kh->desclen + kh->issuerlen > dp)
    201 			continue;
    202 
    203 		struct bytes key = bytesnc(&kh[1], kh->keylen);
    204 		struct bytes desc = bytesnc(key.end, kh->desclen);
    205 		struct bytes issuer = bytesnc(desc.end, kh->issuerlen);
    206 		key_cb(&(struct token){
    207 				key, desc, issuer,
    208 				kh->digest, readbeu64(kh->t0),
    209 				kh->digits, kh->period,
    210 				true
    211 				}, cb_data);
    212 
    213 		dp = decbuf;
    214 	}
    215 }
    216 
    217 int db_add_key(int fd, struct AES_ctx *c,
    218 		struct token *token)
    219 {
    220 	size_t ksz = sizeof(struct totpkey) + bytes_len(token->key) + bytes_len(token->desc) + bytes_len(token->issuer);
    221 	size_t i;
    222 	int w;
    223 
    224 	ksz = (ksz + AES_BLOCKLEN - 1) / AES_BLOCKLEN * AES_BLOCKLEN;
    225 
    226 	if (bytes_len(token->key) > UINT8_MAX || bytes_len(token->desc) > UINT8_MAX || bytes_len(token->issuer) > UINT8_MAX) {
    227 		errno = EMSGSIZE;
    228 		return -1;
    229 	}
    230 
    231 	uint8_t buffer[1024];
    232 	uint8_t *wp = buffer;
    233 	uint64_t t0 = token->t0;
    234 	wp = mempush(wp, &(struct totpkey){
    235 	       .t0 = { t0 >> 56, t0 >> 48, t0 >> 40, t0 >> 32, t0 >> 24, t0 >> 16, t0 >> 8, t0 },
    236 	       .digest = token->digest,
    237 	       .digits = token->digits,
    238 	       .period = token->period,
    239 	       .keylen = bytes_len(token->key),
    240 	       .desclen = bytes_len(token->desc),
    241 	       .issuerlen = bytes_len(token->issuer) }, sizeof(struct totpkey));
    242 	wp = mempushb(wp, token->key);
    243 	wp = mempushb(wp, token->desc);
    244 	wp = mempushb(wp, token->issuer);
    245 	randmem(wp, buffer + ksz - wp);
    246 
    247 	for (i = 0; i < ksz; i += AES_BLOCKLEN)
    248 		AES_CBC_encrypt_buffer(c, buffer + i, AES_BLOCKLEN);
    249 	i = 0;
    250 
    251 	while ((w = write(fd, buffer + i, ksz - i)) > 0)
    252 		i += w;
    253 
    254 	if (w < 0)
    255 		return -errno;
    256 	return i != ksz;
    257 }
    258