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