| Line | Count | Source | 
| 1 |  | /* | 
| 2 |  |  * Copyright (c) 2020-2024 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 | 25.7k | #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 | 24.8k | { | 
| 25 | 24.8k |         uint8_t apdu[5 + UINT8_MAX + 1]; | 
| 26 | 24.8k |         uint8_t sw[2]; | 
| 27 | 24.8k |         size_t apdu_len; | 
| 28 | 24.8k |         int ok = -1; | 
| 29 |  |  | 
| 30 | 24.8k |         memset(&apdu, 0, sizeof(apdu)); | 
| 31 | 24.8k |         apdu[0] = h->cla | cla_flags; | 
| 32 | 24.8k |         apdu[1] = h->ins; | 
| 33 | 24.8k |         apdu[2] = h->p1; | 
| 34 | 24.8k |         apdu[3] = h->p2; | 
| 35 | 24.8k |         apdu[4] = payload_len; | 
| 36 | 24.8k |         memcpy(&apdu[5], payload, payload_len); | 
| 37 | 24.8k |         apdu_len = (size_t)(5 + payload_len + 1); | 
| 38 |  |  | 
| 39 | 24.8k |         if (d->io.write(d->io_handle, apdu, apdu_len) < 0) { | 
| 40 | 603 |                 fido_log_debug("%s: write", __func__); | 
| 41 | 603 |                 goto fail; | 
| 42 | 603 |         } | 
| 43 |  |  | 
| 44 | 24.2k |         if (cla_flags & 0x10) { | 
| 45 | 819 |                 if (d->io.read(d->io_handle, sw, sizeof(sw), -1) != 2) { | 
| 46 | 680 |                         fido_log_debug("%s: read", __func__); | 
| 47 | 680 |                         goto fail; | 
| 48 | 680 |                 } | 
| 49 | 139 |                 if ((sw[0] << 8 | sw[1]) != SW_NO_ERROR) { | 
| 50 | 110 |                         fido_log_debug("%s: unexpected sw", __func__); | 
| 51 | 110 |                         goto fail; | 
| 52 | 110 |                 } | 
| 53 | 139 |         } | 
| 54 |  |  | 
| 55 | 23.4k |         ok = 0; | 
| 56 | 24.8k | fail: | 
| 57 | 24.8k |         explicit_bzero(apdu, sizeof(apdu)); | 
| 58 |  |  | 
| 59 | 24.8k |         return ok; | 
| 60 | 23.4k | } | 
| 61 |  |  | 
| 62 |  | static int | 
| 63 |  | nfc_do_tx(fido_dev_t *d, const uint8_t *apdu_ptr, size_t apdu_len) | 
| 64 | 25.1k | { | 
| 65 | 25.1k |         iso7816_header_t h; | 
| 66 |  |  | 
| 67 | 25.1k |         if (fido_buf_read(&apdu_ptr, &apdu_len, &h, sizeof(h)) < 0) { | 
| 68 | 329 |                 fido_log_debug("%s: header", __func__); | 
| 69 | 329 |                 return -1; | 
| 70 | 329 |         } | 
| 71 | 24.8k |         if (apdu_len < 2) { | 
| 72 | 5 |                 fido_log_debug("%s: apdu_len %zu", __func__, apdu_len); | 
| 73 | 5 |                 return -1; | 
| 74 | 5 |         } | 
| 75 |  |  | 
| 76 | 24.8k |         apdu_len -= 2; /* trim le1 le2 */ | 
| 77 |  |  | 
| 78 | 24.8k |         while (apdu_len > TX_CHUNK_SIZE) { | 
| 79 | 828 |                 if (tx_short_apdu(d, &h, apdu_ptr, TX_CHUNK_SIZE, 0x10) < 0) { | 
| 80 | 799 |                         fido_log_debug("%s: chain", __func__); | 
| 81 | 799 |                         return -1; | 
| 82 | 799 |                 } | 
| 83 | 29 |                 apdu_ptr += TX_CHUNK_SIZE; | 
| 84 | 29 |                 apdu_len -= TX_CHUNK_SIZE; | 
| 85 | 29 |         } | 
| 86 |  |  | 
| 87 | 24.0k |         if (tx_short_apdu(d, &h, apdu_ptr, (uint8_t)apdu_len, 0) < 0) { | 
| 88 | 594 |                 fido_log_debug("%s: tx_short_apdu", __func__); | 
| 89 | 594 |                 return -1; | 
| 90 | 594 |         } | 
| 91 |  |  | 
| 92 | 23.4k |         return 0; | 
| 93 | 24.0k | } | 
| 94 |  |  | 
| 95 |  | int | 
| 96 |  | fido_nfc_tx(fido_dev_t *d, uint8_t cmd, const unsigned char *buf, size_t count) | 
| 97 | 27.0k | { | 
| 98 | 27.0k |         iso7816_apdu_t *apdu = NULL; | 
| 99 | 27.0k |         const uint8_t *ptr; | 
| 100 | 27.0k |         size_t len; | 
| 101 | 27.0k |         int ok = -1; | 
| 102 |  |  | 
| 103 | 27.0k |         switch (cmd) { | 
| 104 | 20.4k |         case CTAP_CMD_INIT: /* select */ | 
| 105 | 20.4k |                 if ((apdu = iso7816_new(0, 0xa4, 0x04, sizeof(aid))) == NULL || | 
| 106 | 20.4k |                     iso7816_add(apdu, aid, sizeof(aid)) < 0) { | 
| 107 | 488 |                         fido_log_debug("%s: iso7816", __func__); | 
| 108 | 488 |                         goto fail; | 
| 109 | 488 |                 } | 
| 110 | 19.9k |                 break; | 
| 111 | 19.9k |         case CTAP_CMD_CBOR: /* wrap cbor */ | 
| 112 | 3.31k |                 if (count > UINT16_MAX || (apdu = iso7816_new(0x80, 0x10, 0x00, | 
| 113 | 3.31k |                     (uint16_t)count)) == NULL || | 
| 114 | 3.31k |                     iso7816_add(apdu, buf, count) < 0) { | 
| 115 | 54 |                         fido_log_debug("%s: iso7816", __func__); | 
| 116 | 54 |                         goto fail; | 
| 117 | 54 |                 } | 
| 118 | 3.26k |                 break; | 
| 119 | 3.26k |         case CTAP_CMD_MSG: /* already an apdu */ | 
| 120 | 1.97k |                 break; | 
| 121 | 1.36k |         default: | 
| 122 | 1.36k |                 fido_log_debug("%s: cmd=%02x", __func__, cmd); | 
| 123 | 1.36k |                 goto fail; | 
| 124 | 27.0k |         } | 
| 125 |  |  | 
| 126 | 25.1k |         if (apdu != NULL) { | 
| 127 | 23.1k |                 ptr = iso7816_ptr(apdu); | 
| 128 | 23.1k |                 len = iso7816_len(apdu); | 
| 129 | 23.1k |         } else { | 
| 130 | 1.97k |                 ptr = buf; | 
| 131 | 1.97k |                 len = count; | 
| 132 | 1.97k |         } | 
| 133 |  |  | 
| 134 | 25.1k |         if (nfc_do_tx(d, ptr, len) < 0) { | 
| 135 | 1.72k |                 fido_log_debug("%s: nfc_do_tx", __func__); | 
| 136 | 1.72k |                 goto fail; | 
| 137 | 1.72k |         } | 
| 138 |  |  | 
| 139 | 23.4k |         ok = 0; | 
| 140 | 27.0k | fail: | 
| 141 | 27.0k |         iso7816_free(&apdu); | 
| 142 |  |  | 
| 143 | 27.0k |         return ok; | 
| 144 | 23.4k | } | 
| 145 |  |  | 
| 146 |  | static int | 
| 147 |  | tx_get_response(fido_dev_t *d, uint8_t count, bool cbor) | 
| 148 | 635 | { | 
| 149 | 635 |         uint8_t apdu[5]; | 
| 150 |  |  | 
| 151 | 635 |         memset(apdu, 0, sizeof(apdu)); | 
| 152 | 635 |         apdu[0] = cbor ? 0x80 : 0x00; | 
| 153 | 635 |         apdu[1] = 0xc0; /* GET_RESPONSE */ | 
| 154 | 635 |         apdu[4] = count; | 
| 155 |  |  | 
| 156 | 635 |         if (d->io.write(d->io_handle, apdu, sizeof(apdu)) < 0) { | 
| 157 | 27 |                 fido_log_debug("%s: write", __func__); | 
| 158 | 27 |                 return -1; | 
| 159 | 27 |         } | 
| 160 |  |  | 
| 161 | 608 |         return 0; | 
| 162 | 635 | } | 
| 163 |  |  | 
| 164 |  | static int | 
| 165 |  | rx_apdu(fido_dev_t *d, uint8_t sw[2], unsigned char **buf, size_t *count, int *ms) | 
| 166 | 23.6k | { | 
| 167 | 23.6k |         uint8_t f[256 + 2]; | 
| 168 | 23.6k |         struct timespec ts; | 
| 169 | 23.6k |         int n, ok = -1; | 
| 170 |  |  | 
| 171 | 23.6k |         if (fido_time_now(&ts) != 0) | 
| 172 | 105 |                 goto fail; | 
| 173 |  |  | 
| 174 | 23.5k |         if ((n = d->io.read(d->io_handle, f, sizeof(f), *ms)) < 2) { | 
| 175 | 16.6k |                 fido_log_debug("%s: read", __func__); | 
| 176 | 16.6k |                 goto fail; | 
| 177 | 16.6k |         } | 
| 178 |  |  | 
| 179 | 6.90k |         if (fido_time_delta(&ts, ms) != 0) | 
| 180 | 58 |                 goto fail; | 
| 181 |  |  | 
| 182 | 6.85k |         if (fido_buf_write(buf, count, f, (size_t)(n - 2)) < 0) { | 
| 183 | 700 |                 fido_log_debug("%s: fido_buf_write", __func__); | 
| 184 | 700 |                 goto fail; | 
| 185 | 700 |         } | 
| 186 |  |  | 
| 187 | 6.15k |         memcpy(sw, f + n - 2, 2); | 
| 188 |  |  | 
| 189 | 6.15k |         ok = 0; | 
| 190 | 23.6k | fail: | 
| 191 | 23.6k |         explicit_bzero(f, sizeof(f)); | 
| 192 |  |  | 
| 193 | 23.6k |         return ok; | 
| 194 | 6.15k | } | 
| 195 |  |  | 
| 196 |  | static int | 
| 197 |  | rx_msg(fido_dev_t *d, unsigned char *buf, size_t count, int ms, bool cbor) | 
| 198 | 23.0k | { | 
| 199 | 23.0k |         uint8_t sw[2]; | 
| 200 | 23.0k |         const size_t bufsiz = count; | 
| 201 |  |  | 
| 202 | 23.0k |         if (rx_apdu(d, sw, &buf, &count, &ms) < 0) { | 
| 203 | 16.8k |                 fido_log_debug("%s: preamble", __func__); | 
| 204 | 16.8k |                 return -1; | 
| 205 | 16.8k |         } | 
| 206 |  |  | 
| 207 | 6.15k |         while (sw[0] == SW1_MORE_DATA) | 
| 208 | 635 |                 if (tx_get_response(d, sw[1], cbor) < 0 || | 
| 209 | 635 |                     rx_apdu(d, sw, &buf, &count, &ms) < 0) { | 
| 210 | 635 |                         fido_log_debug("%s: chain", __func__); | 
| 211 | 635 |                         return -1; | 
| 212 | 635 |                 } | 
| 213 |  |  | 
| 214 | 5.51k |         if (fido_buf_write(&buf, &count, sw, sizeof(sw)) < 0) { | 
| 215 | 56 |                 fido_log_debug("%s: sw", __func__); | 
| 216 | 56 |                 return -1; | 
| 217 | 56 |         } | 
| 218 |  |  | 
| 219 | 5.46k |         if (bufsiz - count > INT_MAX) { | 
| 220 | 0 |                 fido_log_debug("%s: bufsiz", __func__); | 
| 221 | 0 |                 return -1; | 
| 222 | 0 |         } | 
| 223 |  |  | 
| 224 | 5.46k |         return (int)(bufsiz - count); | 
| 225 | 5.46k | } | 
| 226 |  |  | 
| 227 |  | static int | 
| 228 |  | rx_cbor(fido_dev_t *d, unsigned char *buf, size_t count, int ms) | 
| 229 | 3.22k | { | 
| 230 | 3.22k |         int r; | 
| 231 |  |  | 
| 232 | 3.22k |         if ((r = rx_msg(d, buf, count, ms, true)) < 2) | 
| 233 | 2.63k |                 return -1; | 
| 234 |  |  | 
| 235 | 593 |         return r - 2; | 
| 236 | 3.22k | } | 
| 237 |  |  | 
| 238 |  | static int | 
| 239 |  | rx_init(fido_dev_t *d, unsigned char *buf, size_t count, int ms) | 
| 240 | 18.9k | { | 
| 241 | 18.9k |         fido_ctap_info_t *attr = (fido_ctap_info_t *)buf; | 
| 242 | 18.9k |         uint8_t f[64]; | 
| 243 | 18.9k |         int n; | 
| 244 |  |  | 
| 245 | 18.9k |         if (count != sizeof(*attr)) { | 
| 246 | 1.12k |                 fido_log_debug("%s: count=%zu", __func__, count); | 
| 247 | 1.12k |                 return -1; | 
| 248 | 1.12k |         } | 
| 249 |  |  | 
| 250 | 17.8k |         memset(attr, 0, sizeof(*attr)); | 
| 251 |  |  | 
| 252 | 17.8k |         if ((n = rx_msg(d, f, sizeof(f), ms, false)) < 2 || | 
| 253 | 17.8k |             (f[n - 2] << 8 | f[n - 1]) != SW_NO_ERROR) { | 
| 254 | 15.8k |                 fido_log_debug("%s: read", __func__); | 
| 255 | 15.8k |                 return -1; | 
| 256 | 15.8k |         } | 
| 257 |  |  | 
| 258 | 2.05k |         n -= 2; | 
| 259 |  |  | 
| 260 | 2.05k |         if (n == sizeof(v_u2f) && memcmp(f, v_u2f, sizeof(v_u2f)) == 0) | 
| 261 | 25 |                 attr->flags = FIDO_CAP_CBOR; | 
| 262 | 2.02k |         else if (n == sizeof(v_fido) && memcmp(f, v_fido, sizeof(v_fido)) == 0) | 
| 263 | 20 |                 attr->flags = FIDO_CAP_CBOR | FIDO_CAP_NMSG; | 
| 264 | 2.00k |         else { | 
| 265 | 2.00k |                 fido_log_debug("%s: unknown version string", __func__); | 
| 266 | 2.00k | #ifdef FIDO_FUZZ | 
| 267 | 2.00k |                 attr->flags = FIDO_CAP_CBOR | FIDO_CAP_NMSG; | 
| 268 |  | #else | 
| 269 |  |                 return -1; | 
| 270 |  | #endif | 
| 271 | 2.00k |         } | 
| 272 |  |  | 
| 273 | 2.05k |         memcpy(&attr->nonce, &d->nonce, sizeof(attr->nonce)); /* XXX */ | 
| 274 |  |  | 
| 275 | 2.05k |         return (int)count; | 
| 276 | 17.8k | } | 
| 277 |  |  | 
| 278 |  | int | 
| 279 |  | fido_nfc_rx(fido_dev_t *d, uint8_t cmd, unsigned char *buf, size_t count, int ms) | 
| 280 | 25.5k | { | 
| 281 | 25.5k |         switch (cmd) { | 
| 282 | 18.9k |         case CTAP_CMD_INIT: | 
| 283 | 18.9k |                 return rx_init(d, buf, count, ms); | 
| 284 | 3.22k |         case CTAP_CMD_CBOR: | 
| 285 | 3.22k |                 return rx_cbor(d, buf, count, ms); | 
| 286 | 1.95k |         case CTAP_CMD_MSG: | 
| 287 | 1.95k |                 return rx_msg(d, buf, count, ms, false); | 
| 288 | 1.36k |         default: | 
| 289 | 1.36k |                 fido_log_debug("%s: cmd=%02x", __func__, cmd); | 
| 290 | 1.36k |                 return -1; | 
| 291 | 25.5k |         } | 
| 292 | 25.5k | } | 
| 293 |  |  | 
| 294 |  | bool | 
| 295 |  | nfc_is_fido(const char *path) | 
| 296 | 2.51M | { | 
| 297 | 2.51M |         bool fido = false; | 
| 298 | 2.51M |         fido_dev_t *d; | 
| 299 | 2.51M |         int r; | 
| 300 |  |  | 
| 301 | 2.51M |         if ((d = fido_dev_new()) == NULL) { | 
| 302 | 6.22k |                 fido_log_debug("%s: fido_dev_new", __func__); | 
| 303 | 6.22k |                 goto fail; | 
| 304 | 6.22k |         } | 
| 305 |  |         /* fido_dev_open selects the fido applet */ | 
| 306 | 2.51M |         if ((r = fido_dev_open(d, path)) != FIDO_OK) { | 
| 307 | 2.51M |                 fido_log_debug("%s: fido_dev_open: 0x%x", __func__, r); | 
| 308 | 2.51M |                 goto fail; | 
| 309 | 2.51M |         } | 
| 310 | 1.00k |         if ((r = fido_dev_close(d)) != FIDO_OK) { | 
| 311 | 0 |                 fido_log_debug("%s: fido_dev_close: 0x%x", __func__, r); | 
| 312 | 0 |                 goto fail; | 
| 313 |  | 
 | 
| 314 | 0 |         } | 
| 315 |  |  | 
| 316 | 1.00k |         fido = true; | 
| 317 | 2.51M | fail: | 
| 318 | 2.51M |         fido_dev_free(&d); | 
| 319 |  |  | 
| 320 | 2.51M |         return fido; | 
| 321 | 1.00k | } | 
| 322 |  |  | 
| 323 |  | #ifdef USE_NFC | 
| 324 |  | bool | 
| 325 |  | fido_is_nfc(const char *path) | 
| 326 | 3.34M | { | 
| 327 | 3.34M |         return strncmp(path, FIDO_NFC_PREFIX, strlen(FIDO_NFC_PREFIX)) == 0; | 
| 328 | 3.34M | } | 
| 329 |  |  | 
| 330 |  | int | 
| 331 |  | fido_dev_set_nfc(fido_dev_t *d) | 
| 332 | 2.49M | { | 
| 333 | 2.49M |         if (d->io_handle != NULL) { | 
| 334 | 0 |                 fido_log_debug("%s: device open", __func__); | 
| 335 | 0 |                 return -1; | 
| 336 | 0 |         } | 
| 337 | 2.49M |         d->io_own = true; | 
| 338 | 2.49M |         d->io = (fido_dev_io_t) { | 
| 339 | 2.49M |                 fido_nfc_open, | 
| 340 | 2.49M |                 fido_nfc_close, | 
| 341 | 2.49M |                 fido_nfc_read, | 
| 342 | 2.49M |                 fido_nfc_write, | 
| 343 | 2.49M |         }; | 
| 344 | 2.49M |         d->transport = (fido_dev_transport_t) { | 
| 345 | 2.49M |                 fido_nfc_rx, | 
| 346 | 2.49M |                 fido_nfc_tx, | 
| 347 | 2.49M |         }; | 
| 348 |  |  | 
| 349 | 2.49M |         return 0; | 
| 350 | 2.49M | } | 
| 351 |  | #endif /* USE_NFC */ |