1 /*
2 * oathtool.c - command line tool for OATH one-time passwords
3 * Copyright (C) 2009-2016 Simon Josefsson
4 *
5 * This program is free software: you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License as
7 * published by the Free Software Foundation, either version 3 of the
8 * License, or (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see
17 * <http://www.gnu.org/licenses/>.
18 *
19 */
20
21 #include <config.h>
22
23 #include "oath.h"
24
25 #include <stdlib.h>
26 #include <string.h>
27 #include <errno.h>
28 #include <inttypes.h>
29
30 /* Gnulib. */
31 #include "progname.h"
32 #include "error.h"
33 #include "version-etc.h"
34 #include "parse-duration.h"
35 #include "parse-datetime.h"
36
37 #include "oathtool_cmd.h"
38
39 const char version_etc_copyright[] =
40 /* Do *not* mark this string for translation. %s is a copyright
41 symbol suitable for this locale, and %d is the copyright
42 year. */
43 "Copyright %s %d Simon Josefsson.";
44
45 /* This feature is available in gcc versions 2.5 and later. */
46 #if __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ < 5)
47 #define OATH_ATTR_NO_RETURN
48 #else
49 #define OATH_ATTR_NO_RETURN __attribute__ ((__noreturn__))
50 #endif
51
52 /* *INDENT-OFF* */
53 static void
54 usage (int status)
55 OATH_ATTR_NO_RETURN;
56 /* *INDENT-ON* */
57
58 static void
usage(int status)59 usage (int status)
60 {
61 if (status != EXIT_SUCCESS)
62 fprintf (stderr, "Try `%s --help' for more information.\n", program_name);
63 else
64 {
65 cmdline_parser_print_help ();
66 emit_bug_reporting_address ();
67 }
68 exit (status);
69 }
70
71 static time_t
parse_time(const char * p,const time_t now)72 parse_time (const char *p, const time_t now)
73 {
74 struct timespec nowspec = { 0, 0 };
75 struct timespec thenspec;
76
77 nowspec.tv_sec = now;
78
79 if (!parse_datetime (&thenspec, p, &nowspec))
80 return BAD_TIME;
81
82 return thenspec.tv_sec;
83 }
84
85 static void
verbose_hotp(uint64_t moving_factor)86 verbose_hotp (uint64_t moving_factor)
87 {
88 printf ("Start counter: 0x%" PRIX64 " (%" PRIu64 ")\n\n",
89 moving_factor, moving_factor);
90 }
91
92 static void
verbose_totp(time_t t0,time_t time_step_size,time_t when)93 verbose_totp (time_t t0, time_t time_step_size, time_t when)
94 {
95 struct tm tmp;
96 char outstr[200];
97
98 if (gmtime_r (&t0, &tmp) == NULL)
99 error (EXIT_FAILURE, 0, "gmtime_r");
100
101 if (strftime (outstr, sizeof (outstr), "%Y-%m-%d %H:%M:%S UTC", &tmp) == 0)
102 error (EXIT_FAILURE, 0, "strftime");
103
104 printf ("Step size (seconds): %ld\n", time_step_size);
105 printf ("Start time: %s (%ld)\n", outstr, t0);
106
107 if (gmtime_r (&when, &tmp) == NULL)
108 error (EXIT_FAILURE, 0, "gmtime_r");
109
110 if (strftime (outstr, sizeof (outstr), "%Y-%m-%d %H:%M:%S UTC", &tmp) == 0)
111 error (EXIT_FAILURE, 0, "strftime");
112
113 printf ("Current time: %s (%ld)\n", outstr, when);
114 printf ("Counter: 0x%lX (%ld)\n\n", (when - t0) / time_step_size,
115 (when - t0) / time_step_size);
116 }
117
118 #define generate_otp_p(n) ((n) == 1)
119 #define validate_otp_p(n) ((n) == 2)
120
121 #define EXIT_OTP_INVALID 2
122
123 int
main(int argc,char * argv[])124 main (int argc, char *argv[])
125 {
126 struct gengetopt_args_info args_info;
127 char *secret;
128 size_t secretlen = 0;
129 int rc;
130 size_t window;
131 uint64_t moving_factor;
132 unsigned digits;
133 char otp[10];
134 time_t now, when, t0, time_step_size;
135 int totpflags = 0;
136
137 set_program_name (argv[0]);
138
139 if (cmdline_parser (argc, argv, &args_info) != 0)
140 return EXIT_FAILURE;
141
142 if (args_info.version_given)
143 {
144 char *p;
145 int l = -1;
146
147 if (strcmp (oath_check_version (NULL), OATH_VERSION) != 0)
148 l = asprintf (&p, "OATH Toolkit liboath.so %s oath.h %s",
149 oath_check_version (NULL), OATH_VERSION);
150 else if (strcmp (OATH_VERSION, PACKAGE_VERSION) != 0)
151 l = asprintf (&p, "OATH Toolkit %s",
152 oath_check_version (NULL), OATH_VERSION);
153 version_etc (stdout, "oathtool", l == -1 ? "OATH Toolkit" : p,
154 PACKAGE_VERSION, "Simon Josefsson", (char *) NULL);
155 if (l != -1)
156 free (p);
157 return EXIT_SUCCESS;
158 }
159
160 if (args_info.help_given)
161 usage (EXIT_SUCCESS);
162
163 if (args_info.inputs_num == 0)
164 {
165 cmdline_parser_print_help ();
166 emit_bug_reporting_address ();
167 return EXIT_SUCCESS;
168 }
169
170 rc = oath_init ();
171 if (rc != OATH_OK)
172 error (EXIT_FAILURE, 0, "liboath initialization failed: %s",
173 oath_strerror (rc));
174
175 if (args_info.base32_flag)
176 {
177 rc = oath_base32_decode (args_info.inputs[0],
178 strlen (args_info.inputs[0]),
179 &secret, &secretlen);
180 if (rc != OATH_OK)
181 error (EXIT_FAILURE, 0, "base32 decoding failed: %s",
182 oath_strerror (rc));
183 }
184 else
185 {
186 secretlen = 1 + strlen (args_info.inputs[0]) / 2;
187 secret = malloc (secretlen);
188 if (!secret)
189 error (EXIT_FAILURE, errno, "malloc");
190
191 rc = oath_hex2bin (args_info.inputs[0], secret, &secretlen);
192 if (rc != OATH_OK)
193 error (EXIT_FAILURE, 0, "hex decoding of secret key failed");
194 }
195
196 if (args_info.counter_orig)
197 moving_factor = args_info.counter_arg;
198 else
199 moving_factor = 0;
200
201 if (args_info.digits_orig)
202 digits = args_info.digits_arg;
203 else
204 digits = 6;
205
206 if (args_info.window_orig)
207 window = args_info.window_arg;
208 else
209 window = 0;
210
211 if (digits != 6 && digits != 7 && digits != 8)
212 error (EXIT_FAILURE, 0, "only digits 6, 7 and 8 are supported");
213
214 if (validate_otp_p (args_info.inputs_num) && !args_info.digits_orig)
215 digits = strlen (args_info.inputs[1]);
216 else if (validate_otp_p (args_info.inputs_num) && args_info.digits_orig &&
217 args_info.digits_arg != strlen (args_info.inputs[1]))
218 error (EXIT_FAILURE, 0,
219 "given one-time password has bad length %d != %ld",
220 args_info.digits_arg, strlen (args_info.inputs[1]));
221
222 if (args_info.inputs_num > 2)
223 error (EXIT_FAILURE, 0, "too many parameters");
224
225 if (args_info.verbose_flag)
226 {
227 char *tmp;
228
229 tmp = malloc (2 * secretlen + 1);
230 if (!tmp)
231 error (EXIT_FAILURE, errno, "malloc");
232
233 oath_bin2hex (secret, secretlen, tmp);
234
235 printf ("Hex secret: %s\n", tmp);
236 free (tmp);
237
238 rc = oath_base32_encode (secret, secretlen, &tmp, NULL);
239 if (rc != OATH_OK)
240 error (EXIT_FAILURE, 0, "base32 encoding failed: %s",
241 oath_strerror (rc));
242
243 printf ("Base32 secret: %s\n", tmp);
244 free (tmp);
245
246 if (args_info.inputs_num == 2)
247 printf ("OTP: %s\n", args_info.inputs[1]);
248 printf ("Digits: %d\n", digits);
249 printf ("Window size: %ld\n", window);
250 }
251
252 if (args_info.totp_given)
253 {
254 now = time (NULL);
255 when = parse_time (args_info.now_arg, now);
256 t0 = parse_time (args_info.start_time_arg, now);
257 time_step_size = parse_duration (args_info.time_step_size_arg);
258
259 if (when == BAD_TIME)
260 error (EXIT_FAILURE, 0, "cannot parse time `%s'", args_info.now_arg);
261
262 if (t0 == BAD_TIME)
263 error (EXIT_FAILURE, 0, "cannot parse time `%s'",
264 args_info.start_time_arg);
265
266 if (time_step_size == BAD_TIME)
267 error (EXIT_FAILURE, 0, "cannot parse time `%s'",
268 args_info.time_step_size_arg);
269
270 if (strcmp (args_info.totp_arg, "sha256") == 0)
271 totpflags = OATH_TOTP_HMAC_SHA256;
272 else if (strcmp (args_info.totp_arg, "sha512") == 0)
273 totpflags = OATH_TOTP_HMAC_SHA512;
274
275 if (args_info.verbose_flag)
276 verbose_totp (t0, time_step_size, when);
277 }
278 else
279 {
280 if (args_info.verbose_flag)
281 verbose_hotp (moving_factor);
282 }
283
284 if (generate_otp_p (args_info.inputs_num) && !args_info.totp_given)
285 {
286 size_t iter = 0;
287
288 do
289 {
290 rc = oath_hotp_generate (secret,
291 secretlen,
292 moving_factor + iter,
293 digits,
294 false, OATH_HOTP_DYNAMIC_TRUNCATION, otp);
295 if (rc != OATH_OK)
296 error (EXIT_FAILURE, 0,
297 "generating one-time password failed (%d)", rc);
298
299 printf ("%s\n", otp);
300 }
301 while (window - iter++ > 0);
302 }
303 else if (generate_otp_p (args_info.inputs_num) && args_info.totp_given)
304 {
305 size_t iter = 0;
306
307 do
308 {
309 rc = oath_totp_generate2 (secret,
310 secretlen,
311 when + iter * time_step_size,
312 time_step_size, t0, digits, totpflags,
313 otp);
314 if (rc != OATH_OK)
315 error (EXIT_FAILURE, 0,
316 "generating one-time password failed (%d)", rc);
317
318 printf ("%s\n", otp);
319 }
320 while (window - iter++ > 0);
321 }
322 else if (validate_otp_p (args_info.inputs_num) && !args_info.totp_given)
323 {
324 rc = oath_hotp_validate (secret,
325 secretlen,
326 moving_factor, window, args_info.inputs[1]);
327 if (rc == OATH_INVALID_OTP)
328 error (EXIT_OTP_INVALID, 0,
329 "password \"%s\" not found in range %ld .. %ld",
330 args_info.inputs[1],
331 (long) moving_factor, (long) moving_factor + window);
332 else if (rc < 0)
333 error (EXIT_FAILURE, 0,
334 "validating one-time password failed (%d)", rc);
335 printf ("%d\n", rc);
336 }
337 else if (validate_otp_p (args_info.inputs_num) && args_info.totp_given)
338 {
339 rc = oath_totp_validate4 (secret,
340 secretlen,
341 when,
342 time_step_size,
343 t0,
344 window,
345 NULL, NULL, totpflags, args_info.inputs[1]);
346 if (rc == OATH_INVALID_OTP)
347 error (EXIT_OTP_INVALID, 0,
348 "password \"%s\" not found in range %ld .. %ld",
349 args_info.inputs[1],
350 (long) ((when - t0) / time_step_size - window / 2),
351 (long) ((when - t0) / time_step_size + window / 2));
352 else if (rc < 0)
353 error (EXIT_FAILURE, 0,
354 "validating one-time password failed (%d)", rc);
355 printf ("%d\n", rc);
356 }
357
358 free (secret);
359 oath_done ();
360
361 return EXIT_SUCCESS;
362 }
363