Home | History | Annotate | Line # | Download | only in src
      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 #define SCARD_PROTOCOL_Tx SCARD_PROTOCOL_ANY
     29 #endif
     30 
     31 #define BUFSIZE 1024	/* in bytes; passed to SCardListReaders() */
     32 #define APDULEN 264	/* 261 rounded up to the nearest multiple of 8 */
     33 #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 {
     46 	LONG s;
     47 	DWORD len;
     48 
     49 	len = BUFSIZE;
     50 	if ((*buf = calloc(1, len)) == NULL)
     51 		goto fail;
     52 	if ((s = SCardListReaders(ctx, NULL, *buf, &len)) != SCARD_S_SUCCESS) {
     53 		fido_log_debug("%s: SCardListReaders 0x%lx", __func__, (long)s);
     54 		goto fail;
     55 	}
     56 	/* sanity check "multi-string" */
     57 	if (len > BUFSIZE || len < 2) {
     58 		fido_log_debug("%s: bogus len=%u", __func__, (unsigned)len);
     59 		goto fail;
     60 	}
     61 	if ((*buf)[len - 1] != 0 || (*buf)[len - 2] != '\0') {
     62 		fido_log_debug("%s: bogus buf", __func__);
     63 		goto fail;
     64 	}
     65 	return (LONG)SCARD_S_SUCCESS;
     66 fail:
     67 	free(*buf);
     68 	*buf = NULL;
     69 
     70 	return (LONG)SCARD_E_NO_READERS_AVAILABLE;
     71 }
     72 
     73 static char *
     74 get_reader(SCARDCONTEXT ctx, const char *path)
     75 {
     76 	char *reader = NULL, *buf = NULL;
     77 	const char prefix[] = FIDO_PCSC_PREFIX "//slot";
     78 	uint64_t n;
     79 
     80 	if (path == NULL)
     81 		goto out;
     82 	if (strncmp(path, prefix, strlen(prefix)) != 0 ||
     83 	    fido_to_uint64(path + strlen(prefix), 10, &n) < 0 ||
     84 	    n > READERS - 1) {
     85 		fido_log_debug("%s: invalid path %s", __func__, path);
     86 		goto out;
     87 	}
     88 	if (list_readers(ctx, &buf) != SCARD_S_SUCCESS) {
     89 		fido_log_debug("%s: list_readers", __func__);
     90 		goto out;
     91 	}
     92 	for (const char *name = buf; *name != 0; name += strlen(name) + 1) {
     93 		if (n == 0) {
     94 			reader = strdup(name);
     95 			goto out;
     96 		}
     97 		n--;
     98 	}
     99 	fido_log_debug("%s: failed to find reader %s", __func__, path);
    100 out:
    101 	free(buf);
    102 
    103 	return reader;
    104 }
    105 
    106 static int
    107 prepare_io_request(DWORD prot, SCARD_IO_REQUEST *req)
    108 {
    109 	switch (prot) {
    110 	case SCARD_PROTOCOL_T0:
    111 		req->dwProtocol = SCARD_PCI_T0->dwProtocol;
    112 		req->cbPciLength = SCARD_PCI_T0->cbPciLength;
    113 		break;
    114 	case SCARD_PROTOCOL_T1:
    115 		req->dwProtocol = SCARD_PCI_T1->dwProtocol;
    116 		req->cbPciLength = SCARD_PCI_T1->cbPciLength;
    117 		break;
    118 	default:
    119 		fido_log_debug("%s: unknown protocol %lu", __func__,
    120 		    (u_long)prot);
    121 		return -1;
    122 	}
    123 
    124 	return 0;
    125 }
    126 
    127 static int
    128 copy_info(fido_dev_info_t *di, SCARDCONTEXT ctx, const char *reader, size_t idx)
    129 {
    130 	SCARDHANDLE h = 0;
    131 	SCARD_IO_REQUEST req;
    132 	DWORD prot = 0;
    133 	LONG s;
    134 	int ok = -1;
    135 
    136 	memset(di, 0, sizeof(*di));
    137 	memset(&req, 0, sizeof(req));
    138 
    139 	if ((s = SCardConnect(ctx, reader, SCARD_SHARE_SHARED,
    140 	    SCARD_PROTOCOL_Tx, &h, &prot)) != SCARD_S_SUCCESS) {
    141 		fido_log_debug("%s: SCardConnect 0x%lx", __func__, (long)s);
    142 		goto fail;
    143 	}
    144 	if (prepare_io_request(prot, &req) < 0) {
    145 		fido_log_debug("%s: prepare_io_request", __func__);
    146 		goto fail;
    147 	}
    148 	if (asprintf(&di->path, "%s//slot%zu", FIDO_PCSC_PREFIX, idx) == -1) {
    149 		di->path = NULL;
    150 		fido_log_debug("%s: asprintf", __func__);
    151 		goto fail;
    152 	}
    153 	if (nfc_is_fido(di->path) == false) {
    154 		fido_log_debug("%s: nfc_is_fido: %s", __func__, di->path);
    155 		goto fail;
    156 	}
    157 	if ((di->manufacturer = strdup("PC/SC")) == NULL ||
    158 	    (di->product = strdup(reader)) == NULL)
    159 		goto fail;
    160 
    161 	ok = 0;
    162 fail:
    163 	if (h != 0)
    164 		SCardDisconnect(h, SCARD_LEAVE_CARD);
    165 	if (ok < 0) {
    166 		free(di->path);
    167 		free(di->manufacturer);
    168 		free(di->product);
    169 		explicit_bzero(di, sizeof(*di));
    170 	}
    171 
    172 	return ok;
    173 }
    174 
    175 int
    176 fido_pcsc_manifest(fido_dev_info_t *devlist, size_t ilen, size_t *olen)
    177 {
    178 	SCARDCONTEXT ctx = 0;
    179 	char *buf = NULL;
    180 	LONG s;
    181 	size_t idx = 0;
    182 	int r = FIDO_ERR_INTERNAL;
    183 
    184 	*olen = 0;
    185 
    186 	if (ilen == 0)
    187 		return FIDO_OK;
    188 	if (devlist == NULL)
    189 		return FIDO_ERR_INVALID_ARGUMENT;
    190 
    191 	if ((s = SCardEstablishContext(SCARD_SCOPE_SYSTEM, NULL, NULL,
    192 	    &ctx)) != SCARD_S_SUCCESS || ctx == 0) {
    193 		fido_log_debug("%s: SCardEstablishContext 0x%lx", __func__,
    194 		    (long)s);
    195 		if (s == (LONG)SCARD_E_NO_SERVICE ||
    196 		    s == (LONG)SCARD_E_NO_SMARTCARD)
    197 			r = FIDO_OK; /* suppress error */
    198 		goto out;
    199 	}
    200 	if ((s = list_readers(ctx, &buf)) != SCARD_S_SUCCESS) {
    201 		fido_log_debug("%s: list_readers 0x%lx", __func__, (long)s);
    202 		if (s == (LONG)SCARD_E_NO_READERS_AVAILABLE)
    203 			r = FIDO_OK; /* suppress error */
    204 		goto out;
    205 	}
    206 
    207 	for (const char *name = buf; *name != 0; name += strlen(name) + 1) {
    208 		if (idx == READERS) {
    209 			fido_log_debug("%s: stopping at %zu readers", __func__,
    210 			    idx);
    211 			r = FIDO_OK;
    212 			goto out;
    213 		}
    214 		if (copy_info(&devlist[*olen], ctx, name, idx++) == 0) {
    215 			devlist[*olen].io = (fido_dev_io_t) {
    216 				fido_pcsc_open,
    217 				fido_pcsc_close,
    218 				fido_pcsc_read,
    219 				fido_pcsc_write,
    220 			};
    221 			devlist[*olen].transport = (fido_dev_transport_t) {
    222 				fido_pcsc_rx,
    223 				fido_pcsc_tx,
    224 			};
    225 			if (++(*olen) == ilen)
    226 				break;
    227 		}
    228 	}
    229 
    230 	r = FIDO_OK;
    231 out:
    232 	free(buf);
    233 	if (ctx != 0)
    234 		SCardReleaseContext(ctx);
    235 
    236 	return r;
    237 }
    238 
    239 void *
    240 fido_pcsc_open(const char *path)
    241 {
    242 	char *reader = NULL;
    243 	struct pcsc *dev = NULL;
    244 	SCARDCONTEXT ctx = 0;
    245 	SCARDHANDLE h = 0;
    246 	SCARD_IO_REQUEST req;
    247 	DWORD prot = 0;
    248 	LONG s;
    249 
    250 	memset(&req, 0, sizeof(req));
    251 
    252 	if ((s = SCardEstablishContext(SCARD_SCOPE_SYSTEM, NULL, NULL,
    253 	    &ctx)) != SCARD_S_SUCCESS || ctx == 0) {
    254 		fido_log_debug("%s: SCardEstablishContext 0x%lx", __func__,
    255 		    (long)s);
    256 		goto fail;
    257 
    258 	}
    259 	if ((reader = get_reader(ctx, path)) == NULL) {
    260 		fido_log_debug("%s: get_reader(%s)", __func__, path);
    261 		goto fail;
    262 	}
    263 	if ((s = SCardConnect(ctx, reader, SCARD_SHARE_SHARED,
    264 	    SCARD_PROTOCOL_Tx, &h, &prot)) != SCARD_S_SUCCESS) {
    265 		fido_log_debug("%s: SCardConnect 0x%lx", __func__, (long)s);
    266 		goto fail;
    267 	}
    268 	if (prepare_io_request(prot, &req) < 0) {
    269 		fido_log_debug("%s: prepare_io_request", __func__);
    270 		goto fail;
    271 	}
    272 	if ((dev = calloc(1, sizeof(*dev))) == NULL)
    273 		goto fail;
    274 
    275 	dev->ctx = ctx;
    276 	dev->h = h;
    277 	dev->req = req;
    278 	ctx = 0;
    279 	h = 0;
    280 fail:
    281 	if (h != 0)
    282 		SCardDisconnect(h, SCARD_LEAVE_CARD);
    283 	if (ctx != 0)
    284 		SCardReleaseContext(ctx);
    285 	free(reader);
    286 
    287 	return dev;
    288 }
    289 
    290 void
    291 fido_pcsc_close(void *handle)
    292 {
    293 	struct pcsc *dev = handle;
    294 
    295 	if (dev->h != 0)
    296 		SCardDisconnect(dev->h, SCARD_LEAVE_CARD);
    297 	if (dev->ctx != 0)
    298 		SCardReleaseContext(dev->ctx);
    299 
    300 	explicit_bzero(dev->rx_buf, sizeof(dev->rx_buf));
    301 	free(dev);
    302 }
    303 
    304 int
    305 fido_pcsc_read(void *handle, unsigned char *buf, size_t len, int ms)
    306 {
    307 	struct pcsc *dev = handle;
    308 	int r;
    309 
    310 	(void)ms;
    311 	if (dev->rx_len == 0 || dev->rx_len > len ||
    312 	    dev->rx_len > sizeof(dev->rx_buf)) {
    313 		fido_log_debug("%s: rx_len", __func__);
    314 		return -1;
    315 	}
    316 	fido_log_xxd(dev->rx_buf, dev->rx_len, "%s: reading", __func__);
    317 	memcpy(buf, dev->rx_buf, dev->rx_len);
    318 	explicit_bzero(dev->rx_buf, sizeof(dev->rx_buf));
    319 	r = (int)dev->rx_len;
    320 	dev->rx_len = 0;
    321 
    322 	return r;
    323 }
    324 
    325 int
    326 fido_pcsc_write(void *handle, const unsigned char *buf, size_t len)
    327 {
    328 	struct pcsc *dev = handle;
    329 	DWORD n;
    330 	LONG s;
    331 
    332 	if (len > INT_MAX) {
    333 		fido_log_debug("%s: len", __func__);
    334 		return -1;
    335 	}
    336 
    337 	explicit_bzero(dev->rx_buf, sizeof(dev->rx_buf));
    338 	dev->rx_len = 0;
    339 	n = (DWORD)sizeof(dev->rx_buf);
    340 
    341 	fido_log_xxd(buf, len, "%s: writing", __func__);
    342 
    343 	if ((s = SCardTransmit(dev->h, &dev->req, buf, (DWORD)len, NULL,
    344 	    dev->rx_buf, &n)) != SCARD_S_SUCCESS) {
    345 		fido_log_debug("%s: SCardTransmit 0x%lx", __func__, (long)s);
    346 		explicit_bzero(dev->rx_buf, sizeof(dev->rx_buf));
    347 		return -1;
    348 	}
    349 	dev->rx_len = (size_t)n;
    350 
    351 	fido_log_xxd(dev->rx_buf, dev->rx_len, "%s: read", __func__);
    352 
    353 	return (int)len;
    354 }
    355 
    356 int
    357 fido_pcsc_tx(fido_dev_t *d, uint8_t cmd, const u_char *buf, size_t count)
    358 {
    359 	return fido_nfc_tx(d, cmd, buf, count);
    360 }
    361 
    362 int
    363 fido_pcsc_rx(fido_dev_t *d, uint8_t cmd, u_char *buf, size_t count, int ms)
    364 {
    365 	return fido_nfc_rx(d, cmd, buf, count, ms);
    366 }
    367 
    368 bool
    369 fido_is_pcsc(const char *path)
    370 {
    371 	return strncmp(path, FIDO_PCSC_PREFIX, strlen(FIDO_PCSC_PREFIX)) == 0;
    372 }
    373 
    374 int
    375 fido_dev_set_pcsc(fido_dev_t *d)
    376 {
    377 	if (d->io_handle != NULL) {
    378 		fido_log_debug("%s: device open", __func__);
    379 		return -1;
    380 	}
    381 	d->io_own = true;
    382 	d->io = (fido_dev_io_t) {
    383 		fido_pcsc_open,
    384 		fido_pcsc_close,
    385 		fido_pcsc_read,
    386 		fido_pcsc_write,
    387 	};
    388 	d->transport = (fido_dev_transport_t) {
    389 		fido_pcsc_rx,
    390 		fido_pcsc_tx,
    391 	};
    392 
    393 	return 0;
    394 }
    395