2 * Copyright (c) 2022, 2023 Omar Polo <op@openbsd.org>
3 * Copyright (c) 2014 Reyk Floeter <reyk@openbsd.org>
5 * Permission to use, copy, modify, and distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
27 #include <openssl/evp.h>
28 #include <openssl/hmac.h>
31 # define pledge(a, b) (0)
35 # define __dead __attribute__((noreturn))
38 #if defined(__FreeBSD__) || defined(__NetBSD__)
39 # include <sys/endian.h>
40 #elif defined(__APPLE__)
41 # include <machine/endian.h>
42 # include <libkern/OSByteOrder.h>
43 # define htobe64(x) OSSwapHostToBigInt64(x)
44 # define be32toh(x) OSSwapBigToHostInt32(x)
52 fprintf(stderr, "usage: %s\n", getprogname());
59 if (c >= 'A' && c <= 'Z')
61 if (c >= '2' && c <= '7')
62 return (c - '2' + 26);
68 b32decode(const char *s, char *q, size_t qlen)
74 memset(val, 0, sizeof(val));
75 for (i = 0; i < 8; ++i) {
78 if ((val[i] = b32c(*s)) == -1)
89 *q++ = (val[0] << 3) | (val[1] >> 2);
90 *q++ = ((val[1] & 0x03) << 6) | (val[2] << 1) | (val[3] >> 4);
91 *q++ = ((val[3] & 0x0F) << 4) | (val[4] >> 1);
92 *q++ = ((val[4] & 0x01) << 7) | (val[5] << 2) | (val[6] >> 3);
93 *q++ = ((val[6] & 0x07) << 5) | val[7];
99 /* adapted from httpd(8) */
101 url_decode(char *url, char *dst)
117 if (!isxdigit((unsigned char)p[1]) ||
118 !isxdigit((unsigned char)p[2]))
125 * We don't have to validate "hex" because it is
126 * guaranteed to include two hex chars followed by nul.
128 x = strtoul(hex, NULL, 16);
137 uri2secret(char *s, int *digits)
139 char *q, *t, *f, *secret = NULL;
141 if ((q = strchr(s, '?')) == NULL)
145 while ((f = strsep(&t, "&")) != NULL) {
146 if (!strncmp(f, "secret=", 7))
148 else if (!strncmp(f, "digits=", 7)) {
152 else if (!strcmp(f, "7"))
154 else if (!strcmp(f, "8"))
157 warnx("invalid number of digits; using 6");
163 if (url_decode(secret, s) == NULL)
164 errx(1, "failed to percent-decode the secret");
169 main(int argc, char **argv)
173 unsigned char md[EVP_MAX_MD_SIZE];
175 char *s, *q, *line = NULL;
183 if (pledge("stdio", NULL) == -1)
186 while ((ch = getopt(argc, argv, "")) != -1) {
198 linelen = getline(&line, &linesize, stdin);
202 errx(1, "no secret provided");
204 for (s = q = line; *s != '\0'; ++s) {
205 if (isspace((unsigned char)*s)) {
213 errx(1, "no secret provided");
215 if (!strncmp(line, "otpauth://", 10) &&
216 uri2secret(line, &digits) == -1)
217 errx(1, "failed to decode otpauth URI");
219 if ((buflen = b32decode(line, buf, sizeof(buf))) == 0)
220 err(1, "can't base32 decode the secret");
222 ct = htobe64(time(NULL) / 30);
224 HMAC(EVP_sha1(), buf, buflen, (unsigned char *)&ct, sizeof(ct),
227 off = md[mdlen - 1] & 0x0F;
229 memcpy(&hash, md + off, sizeof(hash));
230 hash = be32toh(hash);
234 printf("%06d\n", (hash & 0x7FFFFFFF) % 1000000);
237 printf("%07d\n", (hash & 0x7FFFFFFF) % 10000000);
240 printf("%08d\n", (hash & 0x7FFFFFFF) % 100000000);