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