xref: /freebsd/contrib/libfido2/examples/select.c (revision a8089ea5aee578e08acab2438e82fc9a9ae50ed8)
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)
19 static int
20 nanosleep(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 
33 static fido_dev_t *
34 open_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 
58 static int
59 select_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;
163 out:
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 
182 int
183 main(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