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 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 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 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 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 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 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 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 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 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 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 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 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 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