| Line | Count | Source (jump to first uncovered line) | 
| 1 |  | /* | 
| 2 |  |  * Copyright (c) 2020-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 <stdio.h> | 
| 9 |  | #include <string.h> | 
| 10 |  |  | 
| 11 |  | #include "fido.h" | 
| 12 |  | #include "fido/param.h" | 
| 13 |  | #include "iso7816.h" | 
| 14 |  |  | 
| 15 | 5.38k | #define TX_CHUNK_SIZE   240 | 
| 16 |  |  | 
| 17 |  | static const uint8_t aid[] = { 0xa0, 0x00, 0x00, 0x06, 0x47, 0x2f, 0x00, 0x01 }; | 
| 18 |  | static const uint8_t v_u2f[] = { 'U', '2', 'F', '_', 'V', '2' }; | 
| 19 |  | static const uint8_t v_fido[] = { 'F', 'I', 'D', 'O', '_', '2', '_', '0' }; | 
| 20 |  |  | 
| 21 |  | static int | 
| 22 |  | tx_short_apdu(fido_dev_t *d, const iso7816_header_t *h, const uint8_t *payload, | 
| 23 |  |     uint8_t payload_len, uint8_t cla_flags) | 
| 24 | 5.14k | { | 
| 25 | 5.14k |         uint8_t apdu[5 + UINT8_MAX + 1]; | 
| 26 | 5.14k |         uint8_t sw[2]; | 
| 27 | 5.14k |         size_t apdu_len; | 
| 28 | 5.14k |         int ok = -1; | 
| 29 |  |  | 
| 30 | 5.14k |         memset(&apdu, 0, sizeof(apdu)); | 
| 31 | 5.14k |         apdu[0] = h->cla | cla_flags; | 
| 32 | 5.14k |         apdu[1] = h->ins; | 
| 33 | 5.14k |         apdu[2] = h->p1; | 
| 34 | 5.14k |         apdu[3] = h->p2; | 
| 35 | 5.14k |         apdu[4] = payload_len; | 
| 36 | 5.14k |         memcpy(&apdu[5], payload, payload_len); | 
| 37 | 5.14k |         apdu_len = (size_t)(5 + payload_len + 1); | 
| 38 |  |  | 
| 39 | 5.14k |         if (d->io.write(d->io_handle, apdu, apdu_len) < 0) { | 
| 40 | 72 |                 fido_log_debug("%s: write", __func__); | 
| 41 | 72 |                 goto fail; | 
| 42 | 72 |         } | 
| 43 |  |  | 
| 44 | 5.07k |         if (cla_flags & 0x10) { | 
| 45 | 191 |                 if (d->io.read(d->io_handle, sw, sizeof(sw), -1) != 2) { | 
| 46 | 88 |                         fido_log_debug("%s: read", __func__); | 
| 47 | 88 |                         goto fail; | 
| 48 | 88 |                 } | 
| 49 | 103 |                 if ((sw[0] << 8 | sw[1]) != SW_NO_ERROR) { | 
| 50 | 83 |                         fido_log_debug("%s: unexpected sw", __func__); | 
| 51 | 83 |                         goto fail; | 
| 52 | 83 |                 } | 
| 53 | 103 |         } | 
| 54 |  |  | 
| 55 | 4.90k |         ok = 0; | 
| 56 | 5.14k | fail: | 
| 57 | 5.14k |         explicit_bzero(apdu, sizeof(apdu)); | 
| 58 |  |  | 
| 59 | 5.14k |         return ok; | 
| 60 | 4.90k | } | 
| 61 |  |  | 
| 62 |  | static int | 
| 63 |  | nfc_do_tx(fido_dev_t *d, const uint8_t *apdu_ptr, size_t apdu_len) | 
| 64 | 5.22k | { | 
| 65 | 5.22k |         iso7816_header_t h; | 
| 66 |  |  | 
| 67 | 5.22k |         if (fido_buf_read(&apdu_ptr, &apdu_len, &h, sizeof(h)) < 0) { | 
| 68 | 89 |                 fido_log_debug("%s: header", __func__); | 
| 69 | 89 |                 return -1; | 
| 70 | 89 |         } | 
| 71 | 5.13k |         if (apdu_len < 2) { | 
| 72 | 2 |                 fido_log_debug("%s: apdu_len %zu", __func__, apdu_len); | 
| 73 | 2 |                 return -1; | 
| 74 | 2 |         } | 
| 75 |  |  | 
| 76 | 5.12k |         apdu_len -= 2; /* trim le1 le2 */ | 
| 77 |  |  | 
| 78 | 5.14k |         while (apdu_len > TX_CHUNK_SIZE) { | 
| 79 | 196 |                 if (tx_short_apdu(d, &h, apdu_ptr, TX_CHUNK_SIZE, 0x10) < 0) { | 
| 80 | 176 |                         fido_log_debug("%s: chain", __func__); | 
| 81 | 176 |                         return -1; | 
| 82 | 176 |                 } | 
| 83 | 20 |                 apdu_ptr += TX_CHUNK_SIZE; | 
| 84 | 20 |                 apdu_len -= TX_CHUNK_SIZE; | 
| 85 | 20 |         } | 
| 86 |  |  | 
| 87 | 4.95k |         if (tx_short_apdu(d, &h, apdu_ptr, (uint8_t)apdu_len, 0) < 0) { | 
| 88 | 67 |                 fido_log_debug("%s: tx_short_apdu", __func__); | 
| 89 | 67 |                 return -1; | 
| 90 | 67 |         } | 
| 91 |  |  | 
| 92 | 4.88k |         return 0; | 
| 93 | 4.95k | } | 
| 94 |  |  | 
| 95 |  | int | 
| 96 |  | fido_nfc_tx(fido_dev_t *d, uint8_t cmd, const unsigned char *buf, size_t count) | 
| 97 | 5.80k | { | 
| 98 | 5.80k |         iso7816_apdu_t *apdu = NULL; | 
| 99 | 5.80k |         const uint8_t *ptr; | 
| 100 | 5.80k |         size_t len; | 
| 101 | 5.80k |         int ok = -1; | 
| 102 |  |  | 
| 103 | 5.80k |         switch (cmd) { | 
| 104 | 3.08k |         case CTAP_CMD_INIT: /* select */ | 
| 105 | 3.08k |                 if ((apdu = iso7816_new(0, 0xa4, 0x04, sizeof(aid))) == NULL || | 
| 106 | 3.08k |                     iso7816_add(apdu, aid, sizeof(aid)) < 0) { | 
| 107 | 27 |                         fido_log_debug("%s: iso7816", __func__); | 
| 108 | 27 |                         goto fail; | 
| 109 | 27 |                 } | 
| 110 | 3.05k |                 break; | 
| 111 | 3.05k |         case CTAP_CMD_CBOR: /* wrap cbor */ | 
| 112 | 1.52k |                 if (count > UINT16_MAX || (apdu = iso7816_new(0x80, 0x10, 0x00, | 
| 113 | 1.52k |                     (uint16_t)count)) == NULL || | 
| 114 | 1.52k |                     iso7816_add(apdu, buf, count) < 0) { | 
| 115 | 26 |                         fido_log_debug("%s: iso7816", __func__); | 
| 116 | 26 |                         goto fail; | 
| 117 | 26 |                 } | 
| 118 | 1.50k |                 break; | 
| 119 | 1.50k |         case CTAP_CMD_MSG: /* already an apdu */ | 
| 120 | 661 |                 break; | 
| 121 | 527 |         default: | 
| 122 | 527 |                 fido_log_debug("%s: cmd=%02x", __func__, cmd); | 
| 123 | 527 |                 goto fail; | 
| 124 | 5.80k |         } | 
| 125 |  |  | 
| 126 | 5.22k |         if (apdu != NULL) { | 
| 127 | 4.55k |                 ptr = iso7816_ptr(apdu); | 
| 128 | 4.55k |                 len = iso7816_len(apdu); | 
| 129 | 4.55k |         } else { | 
| 130 | 661 |                 ptr = buf; | 
| 131 | 661 |                 len = count; | 
| 132 | 661 |         } | 
| 133 |  |  | 
| 134 | 5.22k |         if (nfc_do_tx(d, ptr, len) < 0) { | 
| 135 | 334 |                 fido_log_debug("%s: nfc_do_tx", __func__); | 
| 136 | 334 |                 goto fail; | 
| 137 | 334 |         } | 
| 138 |  |  | 
| 139 | 4.88k |         ok = 0; | 
| 140 | 5.80k | fail: | 
| 141 | 5.80k |         iso7816_free(&apdu); | 
| 142 |  |  | 
| 143 | 5.80k |         return ok; | 
| 144 | 4.88k | } | 
| 145 |  |  | 
| 146 |  | static int | 
| 147 |  | rx_init(fido_dev_t *d, unsigned char *buf, size_t count, int ms) | 
| 148 | 2.95k | { | 
| 149 | 2.95k |         fido_ctap_info_t *attr = (fido_ctap_info_t *)buf; | 
| 150 | 2.95k |         uint8_t f[64]; | 
| 151 | 2.95k |         int n; | 
| 152 |  |  | 
| 153 | 2.95k |         if (count != sizeof(*attr)) { | 
| 154 | 199 |                 fido_log_debug("%s: count=%zu", __func__, count); | 
| 155 | 199 |                 return -1; | 
| 156 | 199 |         } | 
| 157 |  |  | 
| 158 | 2.75k |         memset(attr, 0, sizeof(*attr)); | 
| 159 |  |  | 
| 160 | 2.75k |         if ((n = d->io.read(d->io_handle, f, sizeof(f), ms)) < 2 || | 
| 161 | 2.75k |             (f[n - 2] << 8 | f[n - 1]) != SW_NO_ERROR) { | 
| 162 | 1.58k |                 fido_log_debug("%s: read", __func__); | 
| 163 | 1.58k |                 return -1; | 
| 164 | 1.58k |         } | 
| 165 |  |  | 
| 166 | 1.17k |         n -= 2; | 
| 167 |  |  | 
| 168 | 1.17k |         if (n == sizeof(v_u2f) && memcmp(f, v_u2f, sizeof(v_u2f)) == 0) | 
| 169 | 8 |                 attr->flags = FIDO_CAP_CBOR; | 
| 170 | 1.16k |         else if (n == sizeof(v_fido) && memcmp(f, v_fido, sizeof(v_fido)) == 0) | 
| 171 | 7 |                 attr->flags = FIDO_CAP_CBOR | FIDO_CAP_NMSG; | 
| 172 | 1.15k |         else { | 
| 173 | 1.15k |                 fido_log_debug("%s: unknown version string", __func__); | 
| 174 | 1.15k | #ifdef FIDO_FUZZ | 
| 175 | 1.15k |                 attr->flags = FIDO_CAP_CBOR | FIDO_CAP_NMSG; | 
| 176 |  | #else | 
| 177 |  |                 return -1; | 
| 178 |  | #endif | 
| 179 | 1.15k |         } | 
| 180 |  |  | 
| 181 | 1.17k |         memcpy(&attr->nonce, &d->nonce, sizeof(attr->nonce)); /* XXX */ | 
| 182 |  |  | 
| 183 | 1.17k |         return (int)count; | 
| 184 | 2.75k | } | 
| 185 |  |  | 
| 186 |  | static int | 
| 187 |  | tx_get_response(fido_dev_t *d, uint8_t count) | 
| 188 | 302 | { | 
| 189 | 302 |         uint8_t apdu[5]; | 
| 190 |  |  | 
| 191 | 302 |         memset(apdu, 0, sizeof(apdu)); | 
| 192 | 302 |         apdu[1] = 0xc0; /* GET_RESPONSE */ | 
| 193 | 302 |         apdu[4] = count; | 
| 194 |  |  | 
| 195 | 302 |         if (d->io.write(d->io_handle, apdu, sizeof(apdu)) < 0) { | 
| 196 | 13 |                 fido_log_debug("%s: write", __func__); | 
| 197 | 13 |                 return -1; | 
| 198 | 13 |         } | 
| 199 |  |  | 
| 200 | 289 |         return 0; | 
| 201 | 302 | } | 
| 202 |  |  | 
| 203 |  | static int | 
| 204 |  | rx_apdu(fido_dev_t *d, uint8_t sw[2], unsigned char **buf, size_t *count, int *ms) | 
| 205 | 2.35k | { | 
| 206 | 2.35k |         uint8_t f[256 + 2]; | 
| 207 | 2.35k |         struct timespec ts; | 
| 208 | 2.35k |         int n, ok = -1; | 
| 209 |  |  | 
| 210 | 2.35k |         if (fido_time_now(&ts) != 0) | 
| 211 | 29 |                 goto fail; | 
| 212 |  |  | 
| 213 | 2.32k |         if ((n = d->io.read(d->io_handle, f, sizeof(f), *ms)) < 2) { | 
| 214 | 1.34k |                 fido_log_debug("%s: read", __func__); | 
| 215 | 1.34k |                 goto fail; | 
| 216 | 1.34k |         } | 
| 217 |  |  | 
| 218 | 980 |         if (fido_time_delta(&ts, ms) != 0) | 
| 219 | 20 |                 goto fail; | 
| 220 |  |  | 
| 221 | 960 |         if (fido_buf_write(buf, count, f, (size_t)(n - 2)) < 0) { | 
| 222 | 10 |                 fido_log_debug("%s: fido_buf_write", __func__); | 
| 223 | 10 |                 goto fail; | 
| 224 | 10 |         } | 
| 225 |  |  | 
| 226 | 950 |         memcpy(sw, f + n - 2, 2); | 
| 227 |  |  | 
| 228 | 950 |         ok = 0; | 
| 229 | 2.35k | fail: | 
| 230 | 2.35k |         explicit_bzero(f, sizeof(f)); | 
| 231 |  |  | 
| 232 | 2.35k |         return ok; | 
| 233 | 950 | } | 
| 234 |  |  | 
| 235 |  | static int | 
| 236 |  | rx_msg(fido_dev_t *d, unsigned char *buf, size_t count, int ms) | 
| 237 | 2.06k | { | 
| 238 | 2.06k |         uint8_t sw[2]; | 
| 239 | 2.06k |         const size_t bufsiz = count; | 
| 240 |  |  | 
| 241 | 2.06k |         if (rx_apdu(d, sw, &buf, &count, &ms) < 0) { | 
| 242 | 1.31k |                 fido_log_debug("%s: preamble", __func__); | 
| 243 | 1.31k |                 return -1; | 
| 244 | 1.31k |         } | 
| 245 |  |  | 
| 246 | 950 |         while (sw[0] == SW1_MORE_DATA) | 
| 247 | 302 |                 if (tx_get_response(d, sw[1]) < 0 || | 
| 248 | 302 |                     rx_apdu(d, sw, &buf, &count, &ms) < 0) { | 
| 249 | 110 |                         fido_log_debug("%s: chain", __func__); | 
| 250 | 110 |                         return -1; | 
| 251 | 110 |                 } | 
| 252 |  |  | 
| 253 | 648 |         if (fido_buf_write(&buf, &count, sw, sizeof(sw)) < 0) { | 
| 254 | 5 |                 fido_log_debug("%s: sw", __func__); | 
| 255 | 5 |                 return -1; | 
| 256 | 5 |         } | 
| 257 |  |  | 
| 258 | 643 |         if (bufsiz - count > INT_MAX) { | 
| 259 | 0 |                 fido_log_debug("%s: bufsiz", __func__); | 
| 260 | 0 |                 return -1; | 
| 261 | 0 |         } | 
| 262 |  |  | 
| 263 | 643 |         return (int)(bufsiz - count); | 
| 264 | 643 | } | 
| 265 |  |  | 
| 266 |  | static int | 
| 267 |  | rx_cbor(fido_dev_t *d, unsigned char *buf, size_t count, int ms) | 
| 268 | 1.44k | { | 
| 269 | 1.44k |         int r; | 
| 270 |  |  | 
| 271 | 1.44k |         if ((r = rx_msg(d, buf, count, ms)) < 2) | 
| 272 | 905 |                 return -1; | 
| 273 |  |  | 
| 274 | 539 |         return r - 2; | 
| 275 | 1.44k | } | 
| 276 |  |  | 
| 277 |  | int | 
| 278 |  | fido_nfc_rx(fido_dev_t *d, uint8_t cmd, unsigned char *buf, size_t count, int ms) | 
| 279 | 5.25k | { | 
| 280 | 5.25k |         switch (cmd) { | 
| 281 | 2.95k |         case CTAP_CMD_INIT: | 
| 282 | 2.95k |                 return rx_init(d, buf, count, ms); | 
| 283 | 1.44k |         case CTAP_CMD_CBOR: | 
| 284 | 1.44k |                 return rx_cbor(d, buf, count, ms); | 
| 285 | 624 |         case CTAP_CMD_MSG: | 
| 286 | 624 |                 return rx_msg(d, buf, count, ms); | 
| 287 | 231 |         default: | 
| 288 | 231 |                 fido_log_debug("%s: cmd=%02x", __func__, cmd); | 
| 289 | 231 |                 return -1; | 
| 290 | 5.25k |         } | 
| 291 | 5.25k | } | 
| 292 |  |  | 
| 293 |  | bool | 
| 294 |  | nfc_is_fido(const char *path) | 
| 295 | 736k | { | 
| 296 | 736k |         bool fido = false; | 
| 297 | 736k |         fido_dev_t *d; | 
| 298 | 736k |         int r; | 
| 299 |  |  | 
| 300 | 736k |         if ((d = fido_dev_new()) == NULL) { | 
| 301 | 1.76k |                 fido_log_debug("%s: fido_dev_new", __func__); | 
| 302 | 1.76k |                 goto fail; | 
| 303 | 1.76k |         } | 
| 304 |  |         /* fido_dev_open selects the fido applet */ | 
| 305 | 734k |         if ((r = fido_dev_open(d, path)) != FIDO_OK) { | 
| 306 | 734k |                 fido_log_debug("%s: fido_dev_open: 0x%x", __func__, r); | 
| 307 | 734k |                 goto fail; | 
| 308 | 734k |         } | 
| 309 | 168 |         if ((r = fido_dev_close(d)) != FIDO_OK) { | 
| 310 | 0 |                 fido_log_debug("%s: fido_dev_close: 0x%x", __func__, r); | 
| 311 | 0 |                 goto fail; | 
| 312 |  | 
 | 
| 313 | 0 |         } | 
| 314 |  |  | 
| 315 | 168 |         fido = true; | 
| 316 | 736k | fail: | 
| 317 | 736k |         fido_dev_free(&d); | 
| 318 |  |  | 
| 319 | 736k |         return fido; | 
| 320 | 168 | } | 
| 321 |  |  | 
| 322 |  | #ifdef USE_NFC | 
| 323 |  | bool | 
| 324 |  | fido_is_nfc(const char *path) | 
| 325 | 849k | { | 
| 326 | 849k |         return strncmp(path, FIDO_NFC_PREFIX, strlen(FIDO_NFC_PREFIX)) == 0; | 
| 327 | 849k | } | 
| 328 |  |  | 
| 329 |  | int | 
| 330 |  | fido_dev_set_nfc(fido_dev_t *d) | 
| 331 | 733k | { | 
| 332 | 733k |         if (d->io_handle != NULL) { | 
| 333 | 0 |                 fido_log_debug("%s: device open", __func__); | 
| 334 | 0 |                 return -1; | 
| 335 | 0 |         } | 
| 336 | 733k |         d->io_own = true; | 
| 337 | 733k |         d->io = (fido_dev_io_t) { | 
| 338 | 733k |                 fido_nfc_open, | 
| 339 | 733k |                 fido_nfc_close, | 
| 340 | 733k |                 fido_nfc_read, | 
| 341 | 733k |                 fido_nfc_write, | 
| 342 | 733k |         }; | 
| 343 | 733k |         d->transport = (fido_dev_transport_t) { | 
| 344 | 733k |                 fido_nfc_rx, | 
| 345 | 733k |                 fido_nfc_tx, | 
| 346 | 733k |         }; | 
| 347 |  |  | 
| 348 | 733k |         return 0; | 
| 349 | 733k | } | 
| 350 |  | #endif /* USE_NFC */ |