xref: /freebsd/contrib/libfido2/src/io.c (revision 66fd12cf4896eb08ad8e7a2627537f84ead84dd3)
1 /*
2  * Copyright (c) 2018 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 "fido.h"
8 #include "packed.h"
9 
10 PACKED_TYPE(frame_t,
11 struct frame {
12 	uint32_t cid; /* channel id */
13 	union {
14 		uint8_t type;
15 		struct {
16 			uint8_t cmd;
17 			uint8_t bcnth;
18 			uint8_t bcntl;
19 			uint8_t data[CTAP_MAX_REPORT_LEN - CTAP_INIT_HEADER_LEN];
20 		} init;
21 		struct {
22 			uint8_t seq;
23 			uint8_t data[CTAP_MAX_REPORT_LEN - CTAP_CONT_HEADER_LEN];
24 		} cont;
25 	} body;
26 })
27 
28 #ifndef MIN
29 #define MIN(x, y) ((x) > (y) ? (y) : (x))
30 #endif
31 
32 static int
33 tx_pkt(fido_dev_t *d, const void *pkt, size_t len, int *ms)
34 {
35 	struct timespec ts;
36 	int n;
37 
38 	if (fido_time_now(&ts) != 0)
39 		return (-1);
40 
41 	n = d->io.write(d->io_handle, pkt, len);
42 
43 	if (fido_time_delta(&ts, ms) != 0)
44 		return (-1);
45 
46 	return (n);
47 }
48 
49 static int
50 tx_empty(fido_dev_t *d, uint8_t cmd, int *ms)
51 {
52 	struct frame	*fp;
53 	unsigned char	 pkt[sizeof(*fp) + 1];
54 	const size_t	 len = d->tx_len + 1;
55 	int		 n;
56 
57 	memset(&pkt, 0, sizeof(pkt));
58 	fp = (struct frame *)(pkt + 1);
59 	fp->cid = d->cid;
60 	fp->body.init.cmd = CTAP_FRAME_INIT | cmd;
61 
62 	if (len > sizeof(pkt) || (n = tx_pkt(d, pkt, len, ms)) < 0 ||
63 	    (size_t)n != len)
64 		return (-1);
65 
66 	return (0);
67 }
68 
69 static size_t
70 tx_preamble(fido_dev_t *d, uint8_t cmd, const void *buf, size_t count, int *ms)
71 {
72 	struct frame	*fp;
73 	unsigned char	 pkt[sizeof(*fp) + 1];
74 	const size_t	 len = d->tx_len + 1;
75 	int		 n;
76 
77 	if (d->tx_len - CTAP_INIT_HEADER_LEN > sizeof(fp->body.init.data))
78 		return (0);
79 
80 	memset(&pkt, 0, sizeof(pkt));
81 	fp = (struct frame *)(pkt + 1);
82 	fp->cid = d->cid;
83 	fp->body.init.cmd = CTAP_FRAME_INIT | cmd;
84 	fp->body.init.bcnth = (count >> 8) & 0xff;
85 	fp->body.init.bcntl = count & 0xff;
86 	count = MIN(count, d->tx_len - CTAP_INIT_HEADER_LEN);
87 	memcpy(&fp->body.init.data, buf, count);
88 
89 	if (len > sizeof(pkt) || (n = tx_pkt(d, pkt, len, ms)) < 0 ||
90 	    (size_t)n != len)
91 		return (0);
92 
93 	return (count);
94 }
95 
96 static size_t
97 tx_frame(fido_dev_t *d, uint8_t seq, const void *buf, size_t count, int *ms)
98 {
99 	struct frame	*fp;
100 	unsigned char	 pkt[sizeof(*fp) + 1];
101 	const size_t	 len = d->tx_len + 1;
102 	int		 n;
103 
104 	if (d->tx_len - CTAP_CONT_HEADER_LEN > sizeof(fp->body.cont.data))
105 		return (0);
106 
107 	memset(&pkt, 0, sizeof(pkt));
108 	fp = (struct frame *)(pkt + 1);
109 	fp->cid = d->cid;
110 	fp->body.cont.seq = seq;
111 	count = MIN(count, d->tx_len - CTAP_CONT_HEADER_LEN);
112 	memcpy(&fp->body.cont.data, buf, count);
113 
114 	if (len > sizeof(pkt) || (n = tx_pkt(d, pkt, len, ms)) < 0 ||
115 	    (size_t)n != len)
116 		return (0);
117 
118 	return (count);
119 }
120 
121 static int
122 tx(fido_dev_t *d, uint8_t cmd, const unsigned char *buf, size_t count, int *ms)
123 {
124 	size_t n, sent;
125 
126 	if ((sent = tx_preamble(d, cmd, buf, count, ms)) == 0) {
127 		fido_log_debug("%s: tx_preamble", __func__);
128 		return (-1);
129 	}
130 
131 	for (uint8_t seq = 0; sent < count; sent += n) {
132 		if (seq & 0x80) {
133 			fido_log_debug("%s: seq & 0x80", __func__);
134 			return (-1);
135 		}
136 		if ((n = tx_frame(d, seq++, buf + sent, count - sent,
137 		    ms)) == 0) {
138 			fido_log_debug("%s: tx_frame", __func__);
139 			return (-1);
140 		}
141 	}
142 
143 	return (0);
144 }
145 
146 static int
147 transport_tx(fido_dev_t *d, uint8_t cmd, const void *buf, size_t count, int *ms)
148 {
149 	struct timespec ts;
150 	int n;
151 
152 	if (fido_time_now(&ts) != 0)
153 		return (-1);
154 
155 	n = d->transport.tx(d, cmd, buf, count);
156 
157 	if (fido_time_delta(&ts, ms) != 0)
158 		return (-1);
159 
160 	return (n);
161 }
162 
163 int
164 fido_tx(fido_dev_t *d, uint8_t cmd, const void *buf, size_t count, int *ms)
165 {
166 	fido_log_debug("%s: dev=%p, cmd=0x%02x", __func__, (void *)d, cmd);
167 	fido_log_xxd(buf, count, "%s", __func__);
168 
169 	if (d->transport.tx != NULL)
170 		return (transport_tx(d, cmd, buf, count, ms));
171 	if (d->io_handle == NULL || d->io.write == NULL || count > UINT16_MAX) {
172 		fido_log_debug("%s: invalid argument", __func__);
173 		return (-1);
174 	}
175 
176 	return (count == 0 ? tx_empty(d, cmd, ms) : tx(d, cmd, buf, count, ms));
177 }
178 
179 static int
180 rx_frame(fido_dev_t *d, struct frame *fp, int *ms)
181 {
182 	struct timespec ts;
183 	int n;
184 
185 	memset(fp, 0, sizeof(*fp));
186 
187 	if (fido_time_now(&ts) != 0)
188 		return (-1);
189 
190 	if (d->rx_len > sizeof(*fp) || (n = d->io.read(d->io_handle,
191 	    (unsigned char *)fp, d->rx_len, *ms)) < 0 || (size_t)n != d->rx_len)
192 		return (-1);
193 
194 	return (fido_time_delta(&ts, ms));
195 }
196 
197 static int
198 rx_preamble(fido_dev_t *d, uint8_t cmd, struct frame *fp, int *ms)
199 {
200 	do {
201 		if (rx_frame(d, fp, ms) < 0)
202 			return (-1);
203 #ifdef FIDO_FUZZ
204 		fp->cid = d->cid;
205 #endif
206 	} while (fp->cid != d->cid || (fp->cid == d->cid &&
207 	    fp->body.init.cmd == (CTAP_FRAME_INIT | CTAP_KEEPALIVE)));
208 
209 	if (d->rx_len > sizeof(*fp))
210 		return (-1);
211 
212 	fido_log_xxd(fp, d->rx_len, "%s", __func__);
213 #ifdef FIDO_FUZZ
214 	fp->body.init.cmd = (CTAP_FRAME_INIT | cmd);
215 #endif
216 
217 	if (fp->cid != d->cid || fp->body.init.cmd != (CTAP_FRAME_INIT | cmd)) {
218 		fido_log_debug("%s: cid (0x%x, 0x%x), cmd (0x%02x, 0x%02x)",
219 		    __func__, fp->cid, d->cid, fp->body.init.cmd, cmd);
220 		return (-1);
221 	}
222 
223 	return (0);
224 }
225 
226 static int
227 rx(fido_dev_t *d, uint8_t cmd, unsigned char *buf, size_t count, int *ms)
228 {
229 	struct frame f;
230 	size_t r, payload_len, init_data_len, cont_data_len;
231 
232 	if (d->rx_len <= CTAP_INIT_HEADER_LEN ||
233 	    d->rx_len <= CTAP_CONT_HEADER_LEN)
234 		return (-1);
235 
236 	init_data_len = d->rx_len - CTAP_INIT_HEADER_LEN;
237 	cont_data_len = d->rx_len - CTAP_CONT_HEADER_LEN;
238 
239 	if (init_data_len > sizeof(f.body.init.data) ||
240 	    cont_data_len > sizeof(f.body.cont.data))
241 		return (-1);
242 
243 	if (rx_preamble(d, cmd, &f, ms) < 0) {
244 		fido_log_debug("%s: rx_preamble", __func__);
245 		return (-1);
246 	}
247 
248 	payload_len = (size_t)((f.body.init.bcnth << 8) | f.body.init.bcntl);
249 	fido_log_debug("%s: payload_len=%zu", __func__, payload_len);
250 
251 	if (count < payload_len) {
252 		fido_log_debug("%s: count < payload_len", __func__);
253 		return (-1);
254 	}
255 
256 	if (payload_len < init_data_len) {
257 		memcpy(buf, f.body.init.data, payload_len);
258 		return ((int)payload_len);
259 	}
260 
261 	memcpy(buf, f.body.init.data, init_data_len);
262 	r = init_data_len;
263 
264 	for (int seq = 0; r < payload_len; seq++) {
265 		if (rx_frame(d, &f, ms) < 0) {
266 			fido_log_debug("%s: rx_frame", __func__);
267 			return (-1);
268 		}
269 
270 		fido_log_xxd(&f, d->rx_len, "%s", __func__);
271 #ifdef FIDO_FUZZ
272 		f.cid = d->cid;
273 		f.body.cont.seq = (uint8_t)seq;
274 #endif
275 
276 		if (f.cid != d->cid || f.body.cont.seq != seq) {
277 			fido_log_debug("%s: cid (0x%x, 0x%x), seq (%d, %d)",
278 			    __func__, f.cid, d->cid, f.body.cont.seq, seq);
279 			return (-1);
280 		}
281 
282 		if (payload_len - r > cont_data_len) {
283 			memcpy(buf + r, f.body.cont.data, cont_data_len);
284 			r += cont_data_len;
285 		} else {
286 			memcpy(buf + r, f.body.cont.data, payload_len - r);
287 			r += payload_len - r; /* break */
288 		}
289 	}
290 
291 	return ((int)r);
292 }
293 
294 static int
295 transport_rx(fido_dev_t *d, uint8_t cmd, void *buf, size_t count, int *ms)
296 {
297 	struct timespec ts;
298 	int n;
299 
300 	if (fido_time_now(&ts) != 0)
301 		return (-1);
302 
303 	n = d->transport.rx(d, cmd, buf, count, *ms);
304 
305 	if (fido_time_delta(&ts, ms) != 0)
306 		return (-1);
307 
308 	return (n);
309 }
310 
311 int
312 fido_rx(fido_dev_t *d, uint8_t cmd, void *buf, size_t count, int *ms)
313 {
314 	int n;
315 
316 	fido_log_debug("%s: dev=%p, cmd=0x%02x, ms=%d", __func__, (void *)d,
317 	    cmd, *ms);
318 
319 	if (d->transport.rx != NULL)
320 		return (transport_rx(d, cmd, buf, count, ms));
321 	if (d->io_handle == NULL || d->io.read == NULL || count > UINT16_MAX) {
322 		fido_log_debug("%s: invalid argument", __func__);
323 		return (-1);
324 	}
325 	if ((n = rx(d, cmd, buf, count, ms)) >= 0)
326 		fido_log_xxd(buf, (size_t)n, "%s", __func__);
327 
328 	return (n);
329 }
330 
331 int
332 fido_rx_cbor_status(fido_dev_t *d, int *ms)
333 {
334 	unsigned char	reply[FIDO_MAXMSG];
335 	int		reply_len;
336 
337 	if ((reply_len = fido_rx(d, CTAP_CMD_CBOR, &reply, sizeof(reply),
338 	    ms)) < 0 || (size_t)reply_len < 1) {
339 		fido_log_debug("%s: fido_rx", __func__);
340 		return (FIDO_ERR_RX);
341 	}
342 
343 	return (reply[0]);
344 }
345