Blame


1 ecf69ddf 2022-10-20 op /*
2 f31cd5a4 2023-01-16 op * Copyright (c) 2022, 2023 Omar Polo <op@openbsd.org>
3 50705614 2023-05-16 op * Copyright (c) 2014 Reyk Floeter <reyk@openbsd.org>
4 ecf69ddf 2022-10-20 op *
5 ecf69ddf 2022-10-20 op * Permission to use, copy, modify, and distribute this software for any
6 ecf69ddf 2022-10-20 op * purpose with or without fee is hereby granted, provided that the above
7 ecf69ddf 2022-10-20 op * copyright notice and this permission notice appear in all copies.
8 ecf69ddf 2022-10-20 op *
9 ecf69ddf 2022-10-20 op * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 ecf69ddf 2022-10-20 op * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 ecf69ddf 2022-10-20 op * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 ecf69ddf 2022-10-20 op * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 ecf69ddf 2022-10-20 op * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 ecf69ddf 2022-10-20 op * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 ecf69ddf 2022-10-20 op * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16 ecf69ddf 2022-10-20 op */
17 ecf69ddf 2022-10-20 op
18 ecf69ddf 2022-10-20 op #include <ctype.h>
19 ecf69ddf 2022-10-20 op #include <err.h>
20 ecf69ddf 2022-10-20 op #include <errno.h>
21 604a321f 2023-05-27 op #include <limits.h>
22 ecf69ddf 2022-10-20 op #include <stdio.h>
23 ecf69ddf 2022-10-20 op #include <stdlib.h>
24 ecf69ddf 2022-10-20 op #include <string.h>
25 ecf69ddf 2022-10-20 op #include <time.h>
26 ecf69ddf 2022-10-20 op #include <unistd.h>
27 ecf69ddf 2022-10-20 op
28 ecf69ddf 2022-10-20 op #include <openssl/evp.h>
29 ecf69ddf 2022-10-20 op #include <openssl/hmac.h>
30 ecf69ddf 2022-10-20 op
31 ecf69ddf 2022-10-20 op #ifndef __OpenBSD__
32 ecf69ddf 2022-10-20 op # define pledge(a, b) (0)
33 ecf69ddf 2022-10-20 op #endif
34 ecf69ddf 2022-10-20 op
35 ecf69ddf 2022-10-20 op #ifndef __dead
36 ecf69ddf 2022-10-20 op # define __dead __attribute__((noreturn))
37 ecf69ddf 2022-10-20 op #endif
38 ecf69ddf 2022-10-20 op
39 9bf09c65 2023-05-16 op #if defined(__FreeBSD__) || defined(__NetBSD__)
40 ecf69ddf 2022-10-20 op # include <sys/endian.h>
41 ecf69ddf 2022-10-20 op #elif defined(__APPLE__)
42 ecf69ddf 2022-10-20 op # include <machine/endian.h>
43 ecf69ddf 2022-10-20 op # include <libkern/OSByteOrder.h>
44 ecf69ddf 2022-10-20 op # define htobe64(x) OSSwapHostToBigInt64(x)
45 ecf69ddf 2022-10-20 op # define be32toh(x) OSSwapBigToHostInt32(x)
46 ecf69ddf 2022-10-20 op #else
47 ecf69ddf 2022-10-20 op # include <endian.h>
48 ecf69ddf 2022-10-20 op #endif
49 ecf69ddf 2022-10-20 op
50 ecf69ddf 2022-10-20 op static __dead void
51 604a321f 2023-05-27 op usage(const char *argv0)
52 ecf69ddf 2022-10-20 op {
53 604a321f 2023-05-27 op const char *me;
54 604a321f 2023-05-27 op
55 604a321f 2023-05-27 op if ((me = strrchr(argv0, '/')) != NULL)
56 604a321f 2023-05-27 op me++;
57 604a321f 2023-05-27 op else
58 604a321f 2023-05-27 op me = argv0;
59 604a321f 2023-05-27 op
60 604a321f 2023-05-27 op fprintf(stderr, "usage: %s\n", me);
61 ecf69ddf 2022-10-20 op exit(1);
62 ecf69ddf 2022-10-20 op }
63 ecf69ddf 2022-10-20 op
64 ecf69ddf 2022-10-20 op static int
65 ecf69ddf 2022-10-20 op b32c(unsigned char c)
66 ecf69ddf 2022-10-20 op {
67 ecf69ddf 2022-10-20 op if (c >= 'A' && c <= 'Z')
68 ecf69ddf 2022-10-20 op return (c - 'A');
69 ecf69ddf 2022-10-20 op if (c >= '2' && c <= '7')
70 ecf69ddf 2022-10-20 op return (c - '2' + 26);
71 ecf69ddf 2022-10-20 op errno = EINVAL;
72 ecf69ddf 2022-10-20 op return (-1);
73 ecf69ddf 2022-10-20 op }
74 ecf69ddf 2022-10-20 op
75 ecf69ddf 2022-10-20 op static size_t
76 cc5f172a 2022-10-25 op b32decode(const char *s, char *q, size_t qlen)
77 ecf69ddf 2022-10-20 op {
78 ecf69ddf 2022-10-20 op int i, val[8];
79 ecf69ddf 2022-10-20 op char *t = q;
80 ecf69ddf 2022-10-20 op
81 cc5f172a 2022-10-25 op while (*s != '\0') {
82 ecf69ddf 2022-10-20 op memset(val, 0, sizeof(val));
83 ecf69ddf 2022-10-20 op for (i = 0; i < 8; ++i) {
84 cc5f172a 2022-10-25 op if (*s == '\0')
85 ecf69ddf 2022-10-20 op break;
86 ecf69ddf 2022-10-20 op if ((val[i] = b32c(*s)) == -1)
87 ecf69ddf 2022-10-20 op return (0);
88 cc5f172a 2022-10-25 op s++;
89 ecf69ddf 2022-10-20 op }
90 ecf69ddf 2022-10-20 op
91 ecf69ddf 2022-10-20 op if (qlen < 5) {
92 ecf69ddf 2022-10-20 op errno = ENOSPC;
93 ecf69ddf 2022-10-20 op return (0);
94 ecf69ddf 2022-10-20 op }
95 ecf69ddf 2022-10-20 op qlen -= 5;
96 ecf69ddf 2022-10-20 op
97 ecf69ddf 2022-10-20 op *q++ = (val[0] << 3) | (val[1] >> 2);
98 ecf69ddf 2022-10-20 op *q++ = ((val[1] & 0x03) << 6) | (val[2] << 1) | (val[3] >> 4);
99 ecf69ddf 2022-10-20 op *q++ = ((val[3] & 0x0F) << 4) | (val[4] >> 1);
100 ecf69ddf 2022-10-20 op *q++ = ((val[4] & 0x01) << 7) | (val[5] << 2) | (val[6] >> 3);
101 ecf69ddf 2022-10-20 op *q++ = ((val[6] & 0x07) << 5) | val[7];
102 ecf69ddf 2022-10-20 op }
103 ecf69ddf 2022-10-20 op
104 ecf69ddf 2022-10-20 op return (q - t);
105 ecf69ddf 2022-10-20 op }
106 ecf69ddf 2022-10-20 op
107 50705614 2023-05-16 op /* adapted from httpd(8) */
108 50705614 2023-05-16 op static char *
109 50705614 2023-05-16 op url_decode(char *url, char *dst)
110 50705614 2023-05-16 op {
111 50705614 2023-05-16 op char *p, *q;
112 50705614 2023-05-16 op char hex[3];
113 50705614 2023-05-16 op unsigned long x;
114 50705614 2023-05-16 op
115 50705614 2023-05-16 op hex[2] = '\0';
116 50705614 2023-05-16 op p = url;
117 50705614 2023-05-16 op q = dst;
118 50705614 2023-05-16 op
119 50705614 2023-05-16 op while (*p != '\0') {
120 50705614 2023-05-16 op if (*p != '%') {
121 50705614 2023-05-16 op *q++ = *p++;
122 50705614 2023-05-16 op continue;
123 50705614 2023-05-16 op }
124 50705614 2023-05-16 op
125 50705614 2023-05-16 op if (!isxdigit((unsigned char)p[1]) ||
126 50705614 2023-05-16 op !isxdigit((unsigned char)p[2]))
127 50705614 2023-05-16 op return (NULL);
128 50705614 2023-05-16 op
129 50705614 2023-05-16 op hex[0] = p[1];
130 50705614 2023-05-16 op hex[1] = p[2];
131 50705614 2023-05-16 op
132 50705614 2023-05-16 op /*
133 50705614 2023-05-16 op * We don't have to validate "hex" because it is
134 50705614 2023-05-16 op * guaranteed to include two hex chars followed by nul.
135 50705614 2023-05-16 op */
136 50705614 2023-05-16 op x = strtoul(hex, NULL, 16);
137 50705614 2023-05-16 op *q++ = (char)x;
138 50705614 2023-05-16 op p += 3;
139 50705614 2023-05-16 op }
140 50705614 2023-05-16 op *q = '\0';
141 50705614 2023-05-16 op return (url);
142 50705614 2023-05-16 op }
143 50705614 2023-05-16 op
144 f31cd5a4 2023-01-16 op static int
145 a5b7d2ee 2023-05-21 op uri2secret(char *s, int *digits, const EVP_MD **alg, int *period)
146 f31cd5a4 2023-01-16 op {
147 604a321f 2023-05-27 op char *q, *t, *f, *ep, *secret = NULL;
148 604a321f 2023-05-27 op long l;
149 f31cd5a4 2023-01-16 op
150 f31cd5a4 2023-01-16 op if ((q = strchr(s, '?')) == NULL)
151 f31cd5a4 2023-01-16 op return (-1);
152 44703d29 2023-05-16 op
153 44703d29 2023-05-16 op t = q + 1;
154 44703d29 2023-05-16 op while ((f = strsep(&t, "&")) != NULL) {
155 44703d29 2023-05-16 op if (!strncmp(f, "secret=", 7))
156 44703d29 2023-05-16 op secret = f + 7;
157 efd5f30c 2023-05-21 op else if (!strncmp(f, "digits=", 7)) {
158 efd5f30c 2023-05-21 op f += 7;
159 efd5f30c 2023-05-21 op if (!strcmp(f, "6"))
160 efd5f30c 2023-05-21 op *digits = 6;
161 efd5f30c 2023-05-21 op else if (!strcmp(f, "7"))
162 efd5f30c 2023-05-21 op *digits = 7;
163 efd5f30c 2023-05-21 op else if (!strcmp(f, "8"))
164 efd5f30c 2023-05-21 op *digits = 8;
165 efd5f30c 2023-05-21 op else
166 efd5f30c 2023-05-21 op warnx("invalid number of digits; using 6");
167 d2c2e549 2023-05-21 op } else if (!strncmp(f, "algorithm=", 10)) {
168 d2c2e549 2023-05-21 op f += 10;
169 d2c2e549 2023-05-21 op if (!strcmp(f, "SHA1"))
170 d2c2e549 2023-05-21 op *alg = EVP_sha1();
171 d2c2e549 2023-05-21 op else if (!strcmp(f, "SHA256"))
172 d2c2e549 2023-05-21 op *alg = EVP_sha256();
173 d2c2e549 2023-05-21 op else if (!strcmp(f, "SHA512"))
174 d2c2e549 2023-05-21 op *alg = EVP_sha512();
175 d2c2e549 2023-05-21 op else
176 d2c2e549 2023-05-21 op warnx("unknown algorithm; using SHA1");
177 a5b7d2ee 2023-05-21 op } else if (!strncmp(f, "period=", 7)) {
178 a5b7d2ee 2023-05-21 op f += 7;
179 604a321f 2023-05-27 op errno = 0;
180 604a321f 2023-05-27 op l = strtol(f, &ep, 10);
181 604a321f 2023-05-27 op if (f[0] == '\0' || *ep != '\0')
182 604a321f 2023-05-27 op err(1, "period is not a number: %s", f);
183 604a321f 2023-05-27 op if (errno == ERANGE && (l == LONG_MAX || l == LONG_MIN))
184 604a321f 2023-05-27 op err(1, "period is way out of range: %s", f);
185 604a321f 2023-05-27 op if (l < 1 || l > 120)
186 604a321f 2023-05-27 op err(1, "period is out of range: %s", f);
187 604a321f 2023-05-27 op *period = l;
188 efd5f30c 2023-05-21 op }
189 44703d29 2023-05-16 op }
190 44703d29 2023-05-16 op
191 44703d29 2023-05-16 op if (secret == NULL)
192 f31cd5a4 2023-01-16 op return (-1);
193 50705614 2023-05-16 op if (url_decode(secret, s) == NULL)
194 50705614 2023-05-16 op errx(1, "failed to percent-decode the secret");
195 f31cd5a4 2023-01-16 op return (0);
196 f31cd5a4 2023-01-16 op }
197 f31cd5a4 2023-01-16 op
198 ecf69ddf 2022-10-20 op int
199 ecf69ddf 2022-10-20 op main(int argc, char **argv)
200 ecf69ddf 2022-10-20 op {
201 ecf69ddf 2022-10-20 op char buf[1024];
202 ecf69ddf 2022-10-20 op size_t buflen;
203 d2c2e549 2023-05-21 op const EVP_MD *alg;
204 ecf69ddf 2022-10-20 op unsigned char md[EVP_MAX_MD_SIZE];
205 ecf69ddf 2022-10-20 op unsigned int mdlen;
206 604a321f 2023-05-27 op const char *argv0;
207 ecf69ddf 2022-10-20 op char *s, *q, *line = NULL;
208 ecf69ddf 2022-10-20 op size_t linesize = 0;
209 ecf69ddf 2022-10-20 op ssize_t linelen;
210 ecf69ddf 2022-10-20 op uint64_t ct;
211 ecf69ddf 2022-10-20 op uint32_t hash;
212 ecf69ddf 2022-10-20 op uint8_t off;
213 a5b7d2ee 2023-05-21 op int ch, digits = 6, period = 30;
214 ecf69ddf 2022-10-20 op
215 ecf69ddf 2022-10-20 op if (pledge("stdio", NULL) == -1)
216 ecf69ddf 2022-10-20 op err(1, "pledge");
217 ecf69ddf 2022-10-20 op
218 604a321f 2023-05-27 op if ((argv0 = argv[0]) == NULL)
219 604a321f 2023-05-27 op argv0 = "totp";
220 604a321f 2023-05-27 op
221 ecf69ddf 2022-10-20 op while ((ch = getopt(argc, argv, "")) != -1) {
222 ecf69ddf 2022-10-20 op switch (ch) {
223 ecf69ddf 2022-10-20 op default:
224 604a321f 2023-05-27 op usage(argv0);
225 ecf69ddf 2022-10-20 op }
226 ecf69ddf 2022-10-20 op }
227 ecf69ddf 2022-10-20 op argc -= optind;
228 ecf69ddf 2022-10-20 op argv += optind;
229 ecf69ddf 2022-10-20 op
230 ecf69ddf 2022-10-20 op if (argc != 0)
231 604a321f 2023-05-27 op usage(argv0);
232 ecf69ddf 2022-10-20 op
233 d2c2e549 2023-05-21 op alg = EVP_sha1();
234 d2c2e549 2023-05-21 op
235 ecf69ddf 2022-10-20 op linelen = getline(&line, &linesize, stdin);
236 ecf69ddf 2022-10-20 op if (linelen == -1) {
237 ecf69ddf 2022-10-20 op if (ferror(stdin))
238 ecf69ddf 2022-10-20 op err(1, "getline");
239 ecf69ddf 2022-10-20 op errx(1, "no secret provided");
240 ecf69ddf 2022-10-20 op }
241 ecf69ddf 2022-10-20 op for (s = q = line; *s != '\0'; ++s) {
242 ecf69ddf 2022-10-20 op if (isspace((unsigned char)*s)) {
243 ecf69ddf 2022-10-20 op linelen--;
244 ecf69ddf 2022-10-20 op continue;
245 ecf69ddf 2022-10-20 op }
246 ecf69ddf 2022-10-20 op *q++ = *s;
247 ecf69ddf 2022-10-20 op }
248 ecf69ddf 2022-10-20 op *q = '\0';
249 ecf69ddf 2022-10-20 op if (linelen < 1)
250 ecf69ddf 2022-10-20 op errx(1, "no secret provided");
251 ecf69ddf 2022-10-20 op
252 efd5f30c 2023-05-21 op if (!strncmp(line, "otpauth://", 10) &&
253 a5b7d2ee 2023-05-21 op uri2secret(line, &digits, &alg, &period) == -1)
254 f31cd5a4 2023-01-16 op errx(1, "failed to decode otpauth URI");
255 f31cd5a4 2023-01-16 op
256 cc5f172a 2022-10-25 op if ((buflen = b32decode(line, buf, sizeof(buf))) == 0)
257 ecf69ddf 2022-10-20 op err(1, "can't base32 decode the secret");
258 ecf69ddf 2022-10-20 op
259 a5b7d2ee 2023-05-21 op ct = htobe64(time(NULL) / period);
260 ecf69ddf 2022-10-20 op
261 d2c2e549 2023-05-21 op HMAC(alg, buf, buflen, (unsigned char *)&ct, sizeof(ct), md, &mdlen);
262 ecf69ddf 2022-10-20 op
263 ecf69ddf 2022-10-20 op off = md[mdlen - 1] & 0x0F;
264 ecf69ddf 2022-10-20 op
265 ecf69ddf 2022-10-20 op memcpy(&hash, md + off, sizeof(hash));
266 ecf69ddf 2022-10-20 op hash = be32toh(hash);
267 ecf69ddf 2022-10-20 op
268 efd5f30c 2023-05-21 op switch (digits) {
269 efd5f30c 2023-05-21 op case 6:
270 efd5f30c 2023-05-21 op printf("%06d\n", (hash & 0x7FFFFFFF) % 1000000);
271 efd5f30c 2023-05-21 op break;
272 efd5f30c 2023-05-21 op case 7:
273 efd5f30c 2023-05-21 op printf("%07d\n", (hash & 0x7FFFFFFF) % 10000000);
274 efd5f30c 2023-05-21 op break;
275 efd5f30c 2023-05-21 op case 8:
276 efd5f30c 2023-05-21 op printf("%08d\n", (hash & 0x7FFFFFFF) % 100000000);
277 efd5f30c 2023-05-21 op break;
278 efd5f30c 2023-05-21 op }
279 efd5f30c 2023-05-21 op
280 ecf69ddf 2022-10-20 op free(line);
281 ecf69ddf 2022-10-20 op return (0);
282 ecf69ddf 2022-10-20 op }