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