commit ecf69ddfaf6785ad1d8a1dbc59fef74df90291a2 from: Omar Polo date: Thu Oct 20 13:15:00 2022 UTC add a companion TOTP utility commit - 0ef1e7870cad1d0201d736a31b48187f53a71a9b commit + ecf69ddfaf6785ad1d8a1dbc59fef74df90291a2 blob - /dev/null blob + bd76790a065310929666c45bb458a7987ca1cacd (mode 644) --- /dev/null +++ totp.1 @@ -0,0 +1,62 @@ +.\" Copyright (c) 2022 Omar Polo +.\" +.\" Permission to use, copy, modify, and distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +.Dd October 20, 2022 +.Dt TOTP 1 +.Os +.Sh NAME +.Nm totp +.Nd time-based one time password generator +.Sh SYNOPSIS +.Nm +.No < Ns Ar secret +.Sh DESCRIPTION +.Nm +is a time-based one time password generator +.Pq TOTP . +It reads a secret from standard input and prints the generated password, +a numeric code, to standard output. +The secret is usually provided by the authenticator +.Pq for e.g.\& a website . +Blanks in the secret string are ignored, but only one line is read. +.Pp +.Nm +uses a period of 30 seconds and generates six digits long codes. +.Sh EXIT STATUS +.Ex -std +.Sh EXAMPLES +.Nm +is meant to be used with +.Xr plass 1 +or similar application: the secret is stored safely in the password +store and then given to +.Nm +using a pipe: +.Bd -literal -offset indent +$ plass cat 2fa/codeberg/op | totp +722524 +.Ed +.Sh SEE ALSO +.Xr plass 1 +.Sh STANDARDS +.Nm +follows the algorithm outlined in RFC 6238 +.Dq TOTP: Time-Based One-Time Password Algorithm +and uses the base32 encoding as defined in RFC 3548 +.Dq The Base16, Base32, and Base64 Data Encodings . +.Sh AUTHORS +.An -nosplit +The +.Nm +utility was written by +.An Omar Polo Aq Mt op@omarpolo.com . blob - /dev/null blob + d2bf3956029a5b1759d0c055abb3920ec1a1c454 (mode 644) --- /dev/null +++ totp.c @@ -0,0 +1,161 @@ +/* + * Copyright (c) 2022 Omar Polo + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#ifndef __OpenBSD__ +# define pledge(a, b) (0) +#endif + +#ifndef __dead +# define __dead __attribute__((noreturn)) +#endif + +#if defined(__FreeBSD__) +# include +#elif defined(__APPLE__) +# include +# include +# define htobe64(x) OSSwapHostToBigInt64(x) +# define be32toh(x) OSSwapBigToHostInt32(x) +#else +# include +#endif + +static __dead void +usage(void) +{ + fprintf(stderr, "usage: %s\n", getprogname()); + exit(1); +} + +static int +b32c(unsigned char c) +{ + if (c >= 'A' && c <= 'Z') + return (c - 'A'); + if (c >= '2' && c <= '7') + return (c - '2' + 26); + errno = EINVAL; + return (-1); +} + +static size_t +b32decode(char *s, size_t slen, char *q, size_t qlen) +{ + int i, val[8]; + char *t = q; + + while (slen > 0) { + memset(val, 0, sizeof(val)); + for (i = 0; i < 8; ++i) { + if (slen == 0) + break; + if ((val[i] = b32c(*s)) == -1) + return (0); + s++, slen--; + } + + if (qlen < 5) { + errno = ENOSPC; + return (0); + } + qlen -= 5; + + *q++ = (val[0] << 3) | (val[1] >> 2); + *q++ = ((val[1] & 0x03) << 6) | (val[2] << 1) | (val[3] >> 4); + *q++ = ((val[3] & 0x0F) << 4) | (val[4] >> 1); + *q++ = ((val[4] & 0x01) << 7) | (val[5] << 2) | (val[6] >> 3); + *q++ = ((val[6] & 0x07) << 5) | val[7]; + } + + return (q - t); +} + +int +main(int argc, char **argv) +{ + char buf[1024]; + size_t buflen; + unsigned char md[EVP_MAX_MD_SIZE]; + unsigned int mdlen; + char *s, *q, *line = NULL; + size_t linesize = 0; + ssize_t linelen; + uint64_t ct; + uint32_t hash; + uint8_t off; + int ch; + + if (pledge("stdio", NULL) == -1) + err(1, "pledge"); + + while ((ch = getopt(argc, argv, "")) != -1) { + switch (ch) { + default: + usage(); + } + } + argc -= optind; + argv += optind; + + if (argc != 0) + usage(); + + linelen = getline(&line, &linesize, stdin); + if (linelen == -1) { + if (ferror(stdin)) + err(1, "getline"); + errx(1, "no secret provided"); + } + for (s = q = line; *s != '\0'; ++s) { + if (isspace((unsigned char)*s)) { + linelen--; + continue; + } + *q++ = *s; + } + *q = '\0'; + if (linelen < 1) + errx(1, "no secret provided"); + + if ((buflen = b32decode(line, linelen, buf, sizeof(buf))) == 0) + err(1, "can't base32 decode the secret"); + + ct = htobe64(time(NULL) / 30); + + HMAC(EVP_sha1(), buf, buflen, (unsigned char *)&ct, sizeof(ct), + md, &mdlen); + + off = md[mdlen - 1] & 0x0F; + + memcpy(&hash, md + off, sizeof(hash)); + hash = be32toh(hash); + printf("%06d\n", (hash & 0x7FFFFFFF) % 1000000); + + free(line); + return (0); +}