1/*
2 * Copyright (c) 2020-2022 Yubico AB. All rights reserved.
3 * Use of this source code is governed by a BSD-style
4 * license that can be found in the LICENSE file.
5 * SPDX-License-Identifier: BSD-2-Clause
6 */
7
8#include <errno.h>
9#include <fido.h>
10#include <stdio.h>
11#include <stdlib.h>
12#include <time.h>
13
14#include "../openbsd-compat/openbsd-compat.h"
15
16#define FIDO_POLL_MS	50
17
18#if defined(_MSC_VER)
19static int
20nanosleep(const struct timespec *rqtp, struct timespec *rmtp)
21{
22	if (rmtp != NULL) {
23		errno = EINVAL;
24		return (-1);
25	}
26
27	Sleep((DWORD)(rqtp->tv_sec * 1000) + (DWORD)(rqtp->tv_nsec / 1000000));
28
29	return (0);
30}
31#endif
32
33static fido_dev_t *
34open_dev(const fido_dev_info_t *di)
35{
36	fido_dev_t	*dev;
37	int		 r;
38
39	if ((dev = fido_dev_new()) == NULL) {
40		warnx("%s: fido_dev_new", __func__);
41		return (NULL);
42	}
43
44	if ((r = fido_dev_open(dev, fido_dev_info_path(di))) != FIDO_OK) {
45		warnx("%s: fido_dev_open %s: %s", __func__,
46		    fido_dev_info_path(di), fido_strerr(r));
47		fido_dev_free(&dev);
48		return (NULL);
49	}
50
51	printf("%s (0x%04x:0x%04x) is %s\n", fido_dev_info_path(di),
52	    fido_dev_info_vendor(di), fido_dev_info_product(di),
53	    fido_dev_is_fido2(dev) ? "fido2" : "u2f");
54
55	return (dev);
56}
57
58static int
59select_dev(const fido_dev_info_t *devlist, size_t ndevs, fido_dev_t **dev,
60    size_t *idx, int secs)
61{
62	const fido_dev_info_t	 *di;
63	fido_dev_t		**devtab;
64	struct timespec		  ts_start;
65	struct timespec		  ts_now;
66	struct timespec		  ts_delta;
67	struct timespec		  ts_pause;
68	size_t			  nopen = 0;
69	int			  touched;
70	int			  r;
71	long			  ms_remain;
72
73	*dev = NULL;
74	*idx = 0;
75
76	printf("%u authenticator(s) detected\n", (unsigned)ndevs);
77
78	if (ndevs == 0)
79		return (0); /* nothing to do */
80
81	if ((devtab = calloc(ndevs, sizeof(*devtab))) == NULL) {
82		warn("%s: calloc", __func__);
83		return (-1);
84	}
85
86	for (size_t i = 0; i < ndevs; i++) {
87		di = fido_dev_info_ptr(devlist, i);
88		if ((devtab[i] = open_dev(di)) != NULL) {
89			*idx = i;
90			nopen++;
91		}
92	}
93
94	printf("%u authenticator(s) opened\n", (unsigned)nopen);
95
96	if (nopen < 2) {
97		if (nopen == 1)
98			*dev = devtab[*idx]; /* single candidate */
99		r = 0;
100		goto out;
101	}
102
103	for (size_t i = 0; i < ndevs; i++) {
104		di = fido_dev_info_ptr(devlist, i);
105		if (devtab[i] == NULL)
106			continue; /* failed to open */
107		if ((r = fido_dev_get_touch_begin(devtab[i])) != FIDO_OK) {
108			warnx("%s: fido_dev_get_touch_begin %s: %s", __func__,
109			    fido_dev_info_path(di), fido_strerr(r));
110			r = -1;
111			goto out;
112		}
113	}
114
115	if (clock_gettime(CLOCK_MONOTONIC, &ts_start) != 0) {
116		warn("%s: clock_gettime", __func__);
117		r = -1;
118		goto out;
119	}
120
121	ts_pause.tv_sec = 0;
122	ts_pause.tv_nsec = 200000000; /* 200ms */
123
124	do {
125		nanosleep(&ts_pause, NULL);
126
127		for (size_t i = 0; i < ndevs; i++) {
128			di = fido_dev_info_ptr(devlist, i);
129			if (devtab[i] == NULL) {
130				/* failed to open or discarded */
131				continue;
132			}
133			if ((r = fido_dev_get_touch_status(devtab[i], &touched,
134			    FIDO_POLL_MS)) != FIDO_OK) {
135				warnx("%s: fido_dev_get_touch_status %s: %s",
136				    __func__, fido_dev_info_path(di),
137				    fido_strerr(r));
138				fido_dev_close(devtab[i]);
139				fido_dev_free(&devtab[i]);
140				continue; /* discard */
141			}
142			if (touched) {
143				*dev = devtab[i];
144				*idx = i;
145				r = 0;
146				goto out;
147			}
148		}
149
150		if (clock_gettime(CLOCK_MONOTONIC, &ts_now) != 0) {
151			warn("%s: clock_gettime", __func__);
152			r = -1;
153			goto out;
154		}
155
156		timespecsub(&ts_now, &ts_start, &ts_delta);
157		ms_remain = (secs * 1000) - ((long)ts_delta.tv_sec * 1000) +
158		    ((long)ts_delta.tv_nsec / 1000000);
159	} while (ms_remain > FIDO_POLL_MS);
160
161	printf("timeout after %d seconds\n", secs);
162	r = -1;
163out:
164	if (r != 0) {
165		*dev = NULL;
166		*idx = 0;
167	}
168
169	for (size_t i = 0; i < ndevs; i++) {
170		if (devtab[i] && devtab[i] != *dev) {
171			fido_dev_cancel(devtab[i]);
172			fido_dev_close(devtab[i]);
173			fido_dev_free(&devtab[i]);
174		}
175	}
176
177	free(devtab);
178
179	return (r);
180}
181
182int
183main(void)
184{
185	const fido_dev_info_t	*di;
186	fido_dev_info_t		*devlist;
187	fido_dev_t		*dev;
188	size_t			 idx;
189	size_t			 ndevs;
190	int			 r;
191
192	fido_init(0);
193
194	if ((devlist = fido_dev_info_new(64)) == NULL)
195		errx(1, "fido_dev_info_new");
196
197	if ((r = fido_dev_info_manifest(devlist, 64, &ndevs)) != FIDO_OK)
198		errx(1, "fido_dev_info_manifest: %s (0x%x)", fido_strerr(r), r);
199	if (select_dev(devlist, ndevs, &dev, &idx, 15) != 0)
200		errx(1, "select_dev");
201	if (dev == NULL)
202		errx(1, "no authenticator found");
203
204	di = fido_dev_info_ptr(devlist, idx);
205	printf("%s: %s by %s (PIN %sset)\n", fido_dev_info_path(di),
206	    fido_dev_info_product_string(di),
207	    fido_dev_info_manufacturer_string(di),
208	    fido_dev_has_pin(dev) ? "" : "un");
209
210	fido_dev_close(dev);
211	fido_dev_free(&dev);
212	fido_dev_info_free(&devlist, ndevs);
213
214	exit(0);
215}
216