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