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