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