Home | History | Annotate | Line # | Download | only in src
      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