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
35struct 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
43static LONG
44list_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;
66fail:
67	free(*buf);
68	*buf = NULL;
69
70	return (LONG)SCARD_E_NO_READERS_AVAILABLE;
71}
72
73static char *
74get_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);
100out:
101	free(buf);
102
103	return reader;
104}
105
106static int
107prepare_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
127static int
128copy_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;
162fail:
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
175int
176fido_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;
231out:
232	free(buf);
233	if (ctx != 0)
234		SCardReleaseContext(ctx);
235
236	return r;
237}
238
239void *
240fido_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;
280fail:
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
290void
291fido_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
304int
305fido_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
325int
326fido_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
356int
357fido_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
362int
363fido_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
368bool
369fido_is_pcsc(const char *path)
370{
371	return strncmp(path, FIDO_PCSC_PREFIX, strlen(FIDO_PCSC_PREFIX)) == 0;
372}
373
374int
375fido_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