| Line | Count | Source (jump to first uncovered line) | 
| 1 |  | /* | 
| 2 |  |  * Copyright (c) 2022 Micro Focus or one of its affiliates. | 
| 3 |  |  * Copyright (c) 2022 Yubico AB. All rights reserved. | 
| 4 |  |  * Use of this source code is governed by a BSD-style | 
| 5 |  |  * license that can be found in the LICENSE file. | 
| 6 |  |  * SPDX-License-Identifier: BSD-2-Clause | 
| 7 |  |  */ | 
| 8 |  |  | 
| 9 |  | #if __APPLE__ | 
| 10 |  | #include <PCSC/wintypes.h> | 
| 11 |  | #include <PCSC/winscard.h> | 
| 12 |  | #else | 
| 13 |  | #include <winscard.h> | 
| 14 |  | #endif /* __APPLE__ */ | 
| 15 |  |  | 
| 16 |  | #include <errno.h> | 
| 17 |  |  | 
| 18 |  | #include "fido.h" | 
| 19 |  | #include "fido/param.h" | 
| 20 |  | #include "iso7816.h" | 
| 21 |  |  | 
| 22 |  | #if defined(_WIN32) && !defined(__MINGW32__) | 
| 23 |  | #define SCardConnect SCardConnectA | 
| 24 |  | #define SCardListReaders SCardListReadersA | 
| 25 |  | #endif | 
| 26 |  |  | 
| 27 |  | #ifndef SCARD_PROTOCOL_Tx | 
| 28 | 3.77k | #define SCARD_PROTOCOL_Tx SCARD_PROTOCOL_ANY | 
| 29 |  | #endif | 
| 30 |  |  | 
| 31 | 12.1k | #define BUFSIZE 1024        /* in bytes; passed to SCardListReaders() */ | 
| 32 |  | #define APDULEN 264     /* 261 rounded up to the nearest multiple of 8 */ | 
| 33 | 4.56k | #define READERS 8        /* maximum number of readers */ | 
| 34 |  |  | 
| 35 |  | struct pcsc { | 
| 36 |  |         SCARDCONTEXT     ctx; | 
| 37 |  |         SCARDHANDLE      h; | 
| 38 |  |         SCARD_IO_REQUEST req; | 
| 39 |  |         uint8_t          rx_buf[APDULEN]; | 
| 40 |  |         size_t           rx_len; | 
| 41 |  | }; | 
| 42 |  |  | 
| 43 |  | static LONG | 
| 44 |  | list_readers(SCARDCONTEXT ctx, char **buf) | 
| 45 | 5.16k | { | 
| 46 | 5.16k |         LONG s; | 
| 47 | 5.16k |         DWORD len; | 
| 48 |  |  | 
| 49 | 5.16k |         len = BUFSIZE; | 
| 50 | 5.16k |         if ((*buf = calloc(1, len)) == NULL) | 
| 51 | 29 |                 goto fail; | 
| 52 | 5.13k |         if ((s = SCardListReaders(ctx, NULL, *buf, &len)) != SCARD_S_SUCCESS) { | 
| 53 | 1.65k |                 fido_log_debug("%s: SCardListReaders 0x%lx", __func__, (long)s); | 
| 54 | 1.65k |                 goto fail; | 
| 55 | 1.65k |         } | 
| 56 |  |         /* sanity check "multi-string" */ | 
| 57 | 3.48k |         if (len > BUFSIZE || len < 2) { | 
| 58 | 368 |                 fido_log_debug("%s: bogus len=%u", __func__, (unsigned)len); | 
| 59 | 368 |                 goto fail; | 
| 60 | 368 |         } | 
| 61 | 3.11k |         if ((*buf)[len - 1] != 0 || (*buf)[len - 2] != '\0') { | 
| 62 | 60 |                 fido_log_debug("%s: bogus buf", __func__); | 
| 63 | 60 |                 goto fail; | 
| 64 | 60 |         } | 
| 65 | 3.05k |         return (LONG)SCARD_S_SUCCESS; | 
| 66 | 2.10k | fail: | 
| 67 | 2.10k |         free(*buf); | 
| 68 | 2.10k |         *buf = NULL; | 
| 69 |  |  | 
| 70 | 2.10k |         return (LONG)SCARD_E_NO_READERS_AVAILABLE; | 
| 71 | 3.11k | } | 
| 72 |  |  | 
| 73 |  | static char * | 
| 74 |  | get_reader(SCARDCONTEXT ctx, const char *path) | 
| 75 | 6.91k | { | 
| 76 | 6.91k |         char *reader = NULL, *buf = NULL; | 
| 77 | 6.91k |         const char prefix[] = FIDO_PCSC_PREFIX "//slot"; | 
| 78 | 6.91k |         uint64_t n; | 
| 79 |  |  | 
| 80 | 6.91k |         if (path == NULL) | 
| 81 | 1.10k |                 goto out; | 
| 82 | 5.80k |         if (strncmp(path, prefix, strlen(prefix)) != 0 || | 
| 83 | 5.80k |             fido_to_uint64(path + strlen(prefix), 10, &n) < 0 || | 
| 84 | 5.80k |             n > READERS - 1) { | 
| 85 | 3.32k |                 fido_log_debug("%s: invalid path %s", __func__, path); | 
| 86 | 3.32k |                 goto out; | 
| 87 | 3.32k |         } | 
| 88 | 2.48k |         if (list_readers(ctx, &buf) != SCARD_S_SUCCESS) { | 
| 89 | 122 |                 fido_log_debug("%s: list_readers", __func__); | 
| 90 | 122 |                 goto out; | 
| 91 | 122 |         } | 
| 92 | 5.21k |         for (const char *name = buf; *name != 0; name += strlen(name) + 1) { | 
| 93 | 5.18k |                 if (n == 0) { | 
| 94 | 2.32k |                         reader = strdup(name); | 
| 95 | 2.32k |                         goto out; | 
| 96 | 2.32k |                 } | 
| 97 | 2.86k |                 n--; | 
| 98 | 2.86k |         } | 
| 99 | 36 |         fido_log_debug("%s: failed to find reader %s", __func__, path); | 
| 100 | 6.91k | out: | 
| 101 | 6.91k |         free(buf); | 
| 102 |  |  | 
| 103 | 6.91k |         return reader; | 
| 104 | 36 | } | 
| 105 |  |  | 
| 106 |  | static int | 
| 107 |  | prepare_io_request(DWORD prot, SCARD_IO_REQUEST *req) | 
| 108 | 3.74k | { | 
| 109 | 3.74k |         switch (prot) { | 
| 110 | 1.84k |         case SCARD_PROTOCOL_T0: | 
| 111 | 1.84k |                 req->dwProtocol = SCARD_PCI_T0->dwProtocol; | 
| 112 | 1.84k |                 req->cbPciLength = SCARD_PCI_T0->cbPciLength; | 
| 113 | 1.84k |                 break; | 
| 114 | 1.85k |         case SCARD_PROTOCOL_T1: | 
| 115 | 1.85k |                 req->dwProtocol = SCARD_PCI_T1->dwProtocol; | 
| 116 | 1.85k |                 req->cbPciLength = SCARD_PCI_T1->cbPciLength; | 
| 117 | 1.85k |                 break; | 
| 118 | 42 |         default: | 
| 119 | 42 |                 fido_log_debug("%s: unknown protocol %lu", __func__, | 
| 120 | 42 |                     (u_long)prot); | 
| 121 | 42 |                 return -1; | 
| 122 | 3.74k |         } | 
| 123 |  |  | 
| 124 | 3.69k |         return 0; | 
| 125 | 3.74k | } | 
| 126 |  |  | 
| 127 |  | static int | 
| 128 |  | copy_info(fido_dev_info_t *di, SCARDCONTEXT ctx, const char *reader, size_t idx) | 
| 129 | 1.46k | { | 
| 130 | 1.46k |         SCARDHANDLE h = 0; | 
| 131 | 1.46k |         SCARD_IO_REQUEST req; | 
| 132 | 1.46k |         DWORD prot = 0; | 
| 133 | 1.46k |         LONG s; | 
| 134 | 1.46k |         int ok = -1; | 
| 135 |  |  | 
| 136 | 1.46k |         memset(di, 0, sizeof(*di)); | 
| 137 | 1.46k |         memset(&req, 0, sizeof(req)); | 
| 138 |  |  | 
| 139 | 1.46k |         if ((s = SCardConnect(ctx, reader, SCARD_SHARE_SHARED, | 
| 140 | 1.46k |             SCARD_PROTOCOL_Tx, &h, &prot)) != SCARD_S_SUCCESS) { | 
| 141 | 14 |                 fido_log_debug("%s: SCardConnect 0x%lx", __func__, (long)s); | 
| 142 | 14 |                 goto fail; | 
| 143 | 14 |         } | 
| 144 | 1.45k |         if (prepare_io_request(prot, &req) < 0) { | 
| 145 | 15 |                 fido_log_debug("%s: prepare_io_request", __func__); | 
| 146 | 15 |                 goto fail; | 
| 147 | 15 |         } | 
| 148 | 1.43k |         if (asprintf(&di->path, "%s//slot%zu", FIDO_PCSC_PREFIX, idx) == -1) { | 
| 149 | 10 |                 di->path = NULL; | 
| 150 | 10 |                 fido_log_debug("%s: asprintf", __func__); | 
| 151 | 10 |                 goto fail; | 
| 152 | 10 |         } | 
| 153 | 1.42k |         if (nfc_is_fido(di->path) == false) { | 
| 154 | 1.25k |                 fido_log_debug("%s: nfc_is_fido: %s", __func__, di->path); | 
| 155 | 1.25k |                 goto fail; | 
| 156 | 1.25k |         } | 
| 157 | 168 |         if ((di->manufacturer = strdup("PC/SC")) == NULL || | 
| 158 | 168 |             (di->product = strdup(reader)) == NULL) | 
| 159 | 2 |                 goto fail; | 
| 160 |  |  | 
| 161 | 166 |         ok = 0; | 
| 162 | 1.46k | fail: | 
| 163 | 1.46k |         if (h != 0) | 
| 164 | 1.45k |                 SCardDisconnect(h, SCARD_LEAVE_CARD); | 
| 165 | 1.46k |         if (ok < 0) { | 
| 166 | 1.29k |                 free(di->path); | 
| 167 | 1.29k |                 free(di->manufacturer); | 
| 168 | 1.29k |                 free(di->product); | 
| 169 | 1.29k |                 explicit_bzero(di, sizeof(*di)); | 
| 170 | 1.29k |         } | 
| 171 |  |  | 
| 172 | 1.46k |         return ok; | 
| 173 | 166 | } | 
| 174 |  |  | 
| 175 |  | int | 
| 176 |  | fido_pcsc_manifest(fido_dev_info_t *devlist, size_t ilen, size_t *olen) | 
| 177 | 5.00k | { | 
| 178 | 5.00k |         SCARDCONTEXT ctx = 0; | 
| 179 | 5.00k |         char *buf = NULL; | 
| 180 | 5.00k |         LONG s; | 
| 181 | 5.00k |         size_t idx = 0; | 
| 182 | 5.00k |         int r = FIDO_ERR_INTERNAL; | 
| 183 |  |  | 
| 184 | 5.00k |         *olen = 0; | 
| 185 |  |  | 
| 186 | 5.00k |         if (ilen == 0) | 
| 187 | 1.15k |                 return FIDO_OK; | 
| 188 | 3.85k |         if (devlist == NULL) | 
| 189 | 1.13k |                 return FIDO_ERR_INVALID_ARGUMENT; | 
| 190 |  |  | 
| 191 | 2.72k |         if ((s = SCardEstablishContext(SCARD_SCOPE_SYSTEM, NULL, NULL, | 
| 192 | 2.72k |             &ctx)) != SCARD_S_SUCCESS || ctx == 0) { | 
| 193 | 35 |                 fido_log_debug("%s: SCardEstablishContext 0x%lx", __func__, | 
| 194 | 35 |                     (long)s); | 
| 195 | 35 |                 if (s == (LONG)SCARD_E_NO_SERVICE || | 
| 196 | 35 |                     s == (LONG)SCARD_E_NO_SMARTCARD) | 
| 197 | 22 |                         r = FIDO_OK; /* suppress error */ | 
| 198 | 35 |                 goto out; | 
| 199 | 35 |         } | 
| 200 | 2.68k |         if ((s = list_readers(ctx, &buf)) != SCARD_S_SUCCESS) { | 
| 201 | 1.98k |                 fido_log_debug("%s: list_readers 0x%lx", __func__, (long)s); | 
| 202 | 1.98k |                 if (s == (LONG)SCARD_E_NO_READERS_AVAILABLE) | 
| 203 | 1.98k |                         r = FIDO_OK; /* suppress error */ | 
| 204 | 1.98k |                 goto out; | 
| 205 | 1.98k |         } | 
| 206 |  |  | 
| 207 | 2.16k |         for (const char *name = buf; *name != 0; name += strlen(name) + 1) { | 
| 208 | 1.47k |                 if (idx == READERS) { | 
| 209 | 5 |                         fido_log_debug("%s: stopping at %zu readers", __func__, | 
| 210 | 5 |                             idx); | 
| 211 | 5 |                         r = FIDO_OK; | 
| 212 | 5 |                         goto out; | 
| 213 | 5 |                 } | 
| 214 | 1.46k |                 if (copy_info(&devlist[*olen], ctx, name, idx++) == 0) { | 
| 215 | 166 |                         devlist[*olen].io = (fido_dev_io_t) { | 
| 216 | 166 |                                 fido_pcsc_open, | 
| 217 | 166 |                                 fido_pcsc_close, | 
| 218 | 166 |                                 fido_pcsc_read, | 
| 219 | 166 |                                 fido_pcsc_write, | 
| 220 | 166 |                         }; | 
| 221 | 166 |                         devlist[*olen].transport = (fido_dev_transport_t) { | 
| 222 | 166 |                                 fido_pcsc_rx, | 
| 223 | 166 |                                 fido_pcsc_tx, | 
| 224 | 166 |                         }; | 
| 225 | 166 |                         if (++(*olen) == ilen) | 
| 226 | 1 |                                 break; | 
| 227 | 166 |                 } | 
| 228 | 1.46k |         } | 
| 229 |  |  | 
| 230 | 695 |         r = FIDO_OK; | 
| 231 | 2.72k | out: | 
| 232 | 2.72k |         free(buf); | 
| 233 | 2.72k |         if (ctx != 0) | 
| 234 | 2.71k |                 SCardReleaseContext(ctx); | 
| 235 |  |  | 
| 236 | 2.72k |         return r; | 
| 237 | 695 | } | 
| 238 |  |  | 
| 239 |  | void * | 
| 240 |  | fido_pcsc_open(const char *path) | 
| 241 | 7.07k | { | 
| 242 | 7.07k |         char *reader = NULL; | 
| 243 | 7.07k |         struct pcsc *dev = NULL; | 
| 244 | 7.07k |         SCARDCONTEXT ctx = 0; | 
| 245 | 7.07k |         SCARDHANDLE h = 0; | 
| 246 | 7.07k |         SCARD_IO_REQUEST req; | 
| 247 | 7.07k |         DWORD prot = 0; | 
| 248 | 7.07k |         LONG s; | 
| 249 |  |  | 
| 250 | 7.07k |         memset(&req, 0, sizeof(req)); | 
| 251 |  |  | 
| 252 | 7.07k |         if ((s = SCardEstablishContext(SCARD_SCOPE_SYSTEM, NULL, NULL, | 
| 253 | 7.07k |             &ctx)) != SCARD_S_SUCCESS || ctx == 0) { | 
| 254 | 160 |                 fido_log_debug("%s: SCardEstablishContext 0x%lx", __func__, | 
| 255 | 160 |                     (long)s); | 
| 256 | 160 |                 goto fail; | 
| 257 |  |  | 
| 258 | 160 |         } | 
| 259 | 6.91k |         if ((reader = get_reader(ctx, path)) == NULL) { | 
| 260 | 4.60k |                 fido_log_debug("%s: get_reader(%s)", __func__, path); | 
| 261 | 4.60k |                 goto fail; | 
| 262 | 4.60k |         } | 
| 263 | 2.30k |         if ((s = SCardConnect(ctx, reader, SCARD_SHARE_SHARED, | 
| 264 | 2.30k |             SCARD_PROTOCOL_Tx, &h, &prot)) != SCARD_S_SUCCESS) { | 
| 265 | 18 |                 fido_log_debug("%s: SCardConnect 0x%lx", __func__, (long)s); | 
| 266 | 18 |                 goto fail; | 
| 267 | 18 |         } | 
| 268 | 2.28k |         if (prepare_io_request(prot, &req) < 0) { | 
| 269 | 27 |                 fido_log_debug("%s: prepare_io_request", __func__); | 
| 270 | 27 |                 goto fail; | 
| 271 | 27 |         } | 
| 272 | 2.26k |         if ((dev = calloc(1, sizeof(*dev))) == NULL) | 
| 273 | 24 |                 goto fail; | 
| 274 |  |  | 
| 275 | 2.23k |         dev->ctx = ctx; | 
| 276 | 2.23k |         dev->h = h; | 
| 277 | 2.23k |         dev->req = req; | 
| 278 | 2.23k |         ctx = 0; | 
| 279 | 2.23k |         h = 0; | 
| 280 | 7.07k | fail: | 
| 281 | 7.07k |         if (h != 0) | 
| 282 | 51 |                 SCardDisconnect(h, SCARD_LEAVE_CARD); | 
| 283 | 7.07k |         if (ctx != 0) | 
| 284 | 4.79k |                 SCardReleaseContext(ctx); | 
| 285 | 7.07k |         free(reader); | 
| 286 |  |  | 
| 287 | 7.07k |         return dev; | 
| 288 | 2.23k | } | 
| 289 |  |  | 
| 290 |  | void | 
| 291 |  | fido_pcsc_close(void *handle) | 
| 292 | 2.23k | { | 
| 293 | 2.23k |         struct pcsc *dev = handle; | 
| 294 |  |  | 
| 295 | 2.23k |         if (dev->h != 0) | 
| 296 | 2.23k |                 SCardDisconnect(dev->h, SCARD_LEAVE_CARD); | 
| 297 | 2.23k |         if (dev->ctx != 0) | 
| 298 | 2.23k |                 SCardReleaseContext(dev->ctx); | 
| 299 |  |  | 
| 300 | 2.23k |         explicit_bzero(dev->rx_buf, sizeof(dev->rx_buf)); | 
| 301 | 2.23k |         free(dev); | 
| 302 | 2.23k | } | 
| 303 |  |  | 
| 304 |  | int | 
| 305 |  | fido_pcsc_read(void *handle, unsigned char *buf, size_t len, int ms) | 
| 306 | 2.02k | { | 
| 307 | 2.02k |         struct pcsc *dev = handle; | 
| 308 | 2.02k |         int r; | 
| 309 |  |  | 
| 310 | 2.02k |         (void)ms; | 
| 311 | 2.02k |         if (dev->rx_len == 0 || dev->rx_len > len || | 
| 312 | 2.02k |             dev->rx_len > sizeof(dev->rx_buf)) { | 
| 313 | 1.06k |                 fido_log_debug("%s: rx_len", __func__); | 
| 314 | 1.06k |                 return -1; | 
| 315 | 1.06k |         } | 
| 316 | 961 |         fido_log_xxd(dev->rx_buf, dev->rx_len, "%s: reading", __func__); | 
| 317 | 961 |         memcpy(buf, dev->rx_buf, dev->rx_len); | 
| 318 | 961 |         explicit_bzero(dev->rx_buf, sizeof(dev->rx_buf)); | 
| 319 | 961 |         r = (int)dev->rx_len; | 
| 320 | 961 |         dev->rx_len = 0; | 
| 321 |  |  | 
| 322 | 961 |         return r; | 
| 323 | 2.02k | } | 
| 324 |  |  | 
| 325 |  | int | 
| 326 |  | fido_pcsc_write(void *handle, const unsigned char *buf, size_t len) | 
| 327 | 3.23k | { | 
| 328 | 3.23k |         struct pcsc *dev = handle; | 
| 329 | 3.23k |         DWORD n; | 
| 330 | 3.23k |         LONG s; | 
| 331 |  |  | 
| 332 | 3.23k |         if (len > INT_MAX) { | 
| 333 | 1.13k |                 fido_log_debug("%s: len", __func__); | 
| 334 | 1.13k |                 return -1; | 
| 335 | 1.13k |         } | 
| 336 |  |  | 
| 337 | 2.10k |         explicit_bzero(dev->rx_buf, sizeof(dev->rx_buf)); | 
| 338 | 2.10k |         dev->rx_len = 0; | 
| 339 | 2.10k |         n = (DWORD)sizeof(dev->rx_buf); | 
| 340 |  |  | 
| 341 | 2.10k |         fido_log_xxd(buf, len, "%s: writing", __func__); | 
| 342 |  |  | 
| 343 | 2.10k |         if ((s = SCardTransmit(dev->h, &dev->req, buf, (DWORD)len, NULL, | 
| 344 | 2.10k |             dev->rx_buf, &n)) != SCARD_S_SUCCESS) { | 
| 345 | 48 |                 fido_log_debug("%s: SCardTransmit 0x%lx", __func__, (long)s); | 
| 346 | 48 |                 explicit_bzero(dev->rx_buf, sizeof(dev->rx_buf)); | 
| 347 | 48 |                 return -1; | 
| 348 | 48 |         } | 
| 349 | 2.05k |         dev->rx_len = (size_t)n; | 
| 350 |  |  | 
| 351 | 2.05k |         fido_log_xxd(dev->rx_buf, dev->rx_len, "%s: read", __func__); | 
| 352 |  |  | 
| 353 | 2.05k |         return (int)len; | 
| 354 | 2.10k | } | 
| 355 |  |  | 
| 356 |  | int | 
| 357 |  | fido_pcsc_tx(fido_dev_t *d, uint8_t cmd, const u_char *buf, size_t count) | 
| 358 | 2.37k | { | 
| 359 | 2.37k |         return fido_nfc_tx(d, cmd, buf, count); | 
| 360 | 2.37k | } | 
| 361 |  |  | 
| 362 |  | int | 
| 363 |  | fido_pcsc_rx(fido_dev_t *d, uint8_t cmd, u_char *buf, size_t count, int ms) | 
| 364 | 2.29k | { | 
| 365 | 2.29k |         return fido_nfc_rx(d, cmd, buf, count, ms); | 
| 366 | 2.29k | } | 
| 367 |  |  | 
| 368 |  | bool | 
| 369 |  | fido_is_pcsc(const char *path) | 
| 370 | 849k | { | 
| 371 | 849k |         return strncmp(path, FIDO_PCSC_PREFIX, strlen(FIDO_PCSC_PREFIX)) == 0; | 
| 372 | 849k | } | 
| 373 |  |  | 
| 374 |  | int | 
| 375 |  | fido_dev_set_pcsc(fido_dev_t *d) | 
| 376 | 5.95k | { | 
| 377 | 5.95k |         if (d->io_handle != NULL) { | 
| 378 | 0 |                 fido_log_debug("%s: device open", __func__); | 
| 379 | 0 |                 return -1; | 
| 380 | 0 |         } | 
| 381 | 5.95k |         d->io_own = true; | 
| 382 | 5.95k |         d->io = (fido_dev_io_t) { | 
| 383 | 5.95k |                 fido_pcsc_open, | 
| 384 | 5.95k |                 fido_pcsc_close, | 
| 385 | 5.95k |                 fido_pcsc_read, | 
| 386 | 5.95k |                 fido_pcsc_write, | 
| 387 | 5.95k |         }; | 
| 388 | 5.95k |         d->transport = (fido_dev_transport_t) { | 
| 389 | 5.95k |                 fido_pcsc_rx, | 
| 390 | 5.95k |                 fido_pcsc_tx, | 
| 391 | 5.95k |         }; | 
| 392 |  |  | 
| 393 | 5.95k |         return 0; | 
| 394 | 5.95k | } |