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