totp

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

commit fb3a7c227e03c7f6436254f45f2768ab8ab8842b
parent c17c586184dd6c3a5f7b98759e77194ad3b5dcca
Author: Santtu Lakkala <santtu.lakkala@digital14.com>
Date:   Thu, 28 Sep 2023 15:29:55 +0300

Improve error reporting

Diffstat:
Mdb.c | 10+++++-----
Mmain.c | 18+++++++++---------
Mtotp.1 | 25+++++++++++++++++++------
Mutil.c | 18++++++++++++++++++
Mutil.h | 9+++++++++
5 files changed, 60 insertions(+), 20 deletions(-)

diff --git a/db.c b/db.c @@ -26,7 +26,7 @@ static bool verify_db_legacy(int fd, struct AES_ctx *c, uint8_t *keybuf) rused += r; if (rused < sizeof(rbuf)) { - errno = ENODATA; + errno = TOTP_EEOF; return false; } @@ -43,7 +43,7 @@ static bool verify_db_legacy(int fd, struct AES_ctx *c, uint8_t *keybuf) h->version == 1) return true; - errno = EPERM; + errno = TOTP_EMALF; return false; } @@ -58,7 +58,7 @@ static bool verify_db(int fd, struct AES_ctx *c, uint8_t *keybuf) rused += r; if (rused < sizeof(rbuf)) { - errno = ENODATA; + errno = TOTP_EEOF; return false; } @@ -69,7 +69,7 @@ static bool verify_db(int fd, struct AES_ctx *c, uint8_t *keybuf) rused += r; if (rused < sizeof(rbuf)) { - errno = ENODATA; + errno = TOTP_EEOF; return false; } @@ -83,7 +83,7 @@ static bool verify_db(int fd, struct AES_ctx *c, uint8_t *keybuf) h->version == 1) return true; - errno = EPERM; + errno = TOTP_EMALF; return false; } diff --git a/main.c b/main.c @@ -348,15 +348,15 @@ int main(int argc, char *argv[]) for (t = strtok(secretfile + 1, "/"); (t = strtok(NULL, "/")); ) { if (mkdir(secretfile, 0700) && errno != EEXIST) - croak("Could not create secret db dir: %s", strerror(errno)); + croak("Could not create secret db dir: %s", totp_strerror(errno)); t[-1] = '/'; } switch (cmd) { case CMD_LIST: fd = db_open_read(secretfile, &c, keybuf); - if (fd < 0) - break; + if (fd < 0 && errno != ENOENT) + croak("Opening existing db failed: %s", totp_strerror(errno)); db_foreach(fd, &c, print_key, stdout); close(fd); break; @@ -368,11 +368,11 @@ int main(int argc, char *argv[]) fd = db_open_read(secretfile, &c, keybuf); if (fd < 0 && errno != ENOENT) - croak("Opening existing db failed: %s", strerror(errno)); + croak("Opening existing db failed: %s", totp_strerror(errno)); wfd = db_open_write(newsecretfile, &wc, keybuf); if (wfd < 0) - croak("Could not open temporary secret file: %s", strerror(errno)); + croak("Could not open temporary secret file: %s", totp_strerror(errno)); if (fd >= 0) { db_foreach(fd, &c, write_filter_key, &(struct write_filter_data){ .fd = wfd, .c = &wc }); @@ -391,7 +391,7 @@ int main(int argc, char *argv[]) case CMD_TOK: fd = db_open_read(secretfile, &c, keybuf); if (fd < 0) - croak("Could not open secret file: %s", strerror(errno)); + croak("Could not open secret file: %s", totp_strerror(errno)); gd.filter = keyquery; db_foreach(fd, &c, generate_token, &gd); close(fd); @@ -404,11 +404,11 @@ int main(int argc, char *argv[]) if (fd < 0) { if (errno == ENOENT) break; - croak("Could not open secret file: %s", strerror(errno)); + croak("Could not open secret file: %s", totp_strerror(errno)); } wfd = db_open_write(newsecretfile, &wc, keybuf); if (wfd < 0) - croak("Could not open temporary secret file: %s", strerror(errno)); + croak("Could not open temporary secret file: %s", totp_strerror(errno)); db_foreach(fd, &c, write_filter_key, &(struct write_filter_data){ .fd = wfd, .filter = keyquery, @@ -422,7 +422,7 @@ int main(int argc, char *argv[]) case CMD_EXP: fd = db_open_read(secretfile, &c, keybuf); if (fd < 0) - croak("Could not open secret file: %s", strerror(errno)); + croak("Could not open secret file: %s", totp_strerror(errno)); db_foreach(fd, &c, print_keyuri, stdout); close(fd); break; diff --git a/totp.1 b/totp.1 @@ -16,6 +16,8 @@ .Op Fl d Ar filter .Op Fl t Ar filter .Op Fl e +.Op Fl T Ar time +.Op Fl f Ar file .Ek . .Sh DESCRIPTION @@ -47,8 +49,16 @@ Remove secrets from database that match filter. Generate authentication tokens with all secrets that match filter. . .It Fl e -Export all secrets from database as uris. +Export all secrets from database as uris. Exported uris will also explicitly +contain default values. . +.It Fl T Ar time +Use time as current unix timestamp. Mainly intended for testing purposes. +. +.It Fl f Ar file +Use file as secrets database (default: ~/.local/share/totp/secrets.db). On +modifying operations, the contained directory must also be writable. + .Sh URI FORMAT URIs follow the google-authenticator Key Uri Format with otpauth protocol: otpauth://totp/accountname?secret=<secret> @@ -57,15 +67,18 @@ The secret should be encoded in RFC3548 Base32 format, without padding. Supported URI query string parameters: .Bl -tag -width "algorithm" .It Ar issuer -Defines the issuing organisation of the secret. +Defines the issuing organisation of the secret. If also defined as part of the +path, values must match. Optional. .It Ar algorithm Defines the digest algorithm used to generate tokens, should be one of SHA1, SHA256, SHA512; defaults to SHA1. .It Ar digits -Defines how many digits the generated token should have, should be 6 or 8. +Defines how many digits the generated token should have, should be in range +6-8, inclusive. Defaults to 6. .It Ar period -Defines the validity period of a token in seconds. Defaults to 30. +Defines the validity period of a token in seconds. Defaults to 30. Normally +15, 30 or 60. . .Sh FILTER In deletion and token generation the secret is chosen by matching the @@ -73,8 +86,8 @@ accountname against the provided filter. The matching is done with fnmatch() so asterisks can be used, but the accountname must fully match the filter. For substring matches, use leading and trailing asterisks. . -If an issuer: prefix is present in accountname during addition, then it will -be included in the matching, but a query string parameter issuer is excluded. +If an issuer is present, then it will be included in the matching as issuer: +-prefix. . .Sh KNOWN BUGS If multiple secrets match a filter in token generation, a token will be diff --git a/util.c b/util.c @@ -6,6 +6,10 @@ #include "util.h" +struct dummy { + int dummy : (TOTP_EMAX == 0) * 2 - 1; +}; + void xormem(void *a, const void *b, size_t len) { uint8_t *wa = a; @@ -14,6 +18,11 @@ void xormem(void *a, const void *b, size_t len) *wa++ ^= *rb++; } +const char *totp_errstr[] = { + "Database ended unexpectedly", + "Malformed database or invalid passphrase", +}; + void hmac(const void *key, size_t keylen, const void *data, size_t datalen, digest_init init, @@ -122,6 +131,15 @@ size_t strnspn(const char *s, size_t l, const char *c) return (const char *)u - s; } +const char *totp_strerror(int err) +{ + if (err < TOTP_EMIN) + return ""; + if (err >= TOTP_EMAX) + return strerror(err); + return totp_errstr[err - TOTP_EMIN]; +} + void croak(const char *fmt, ...) { va_list args; diff --git a/util.h b/util.h @@ -7,6 +7,14 @@ #include <string.h> #include <time.h> +enum totp_error { + TOTP_EEOF = -2, + TOTP_EMALF, + TOTP_EMAX, + + TOTP_EMIN = TOTP_EEOF, +}; + struct bytes { uint8_t *data; uint8_t *end; @@ -107,6 +115,7 @@ static inline char *if_prefix(const char *s, const char *prefix) return (char *)s; } +const char *totp_strerror(int err); void croak(const char *fmt, ...); struct bytes debase32(struct bytes data);