Skip to content

Commit bb83a0e

Browse files
Merge pull request #148 from LedgerHQ/nfc-pcsc
Fix support for PCSC smartcard readers
2 parents 4d2fafc + 65c2710 commit bb83a0e

File tree

2 files changed

+42
-39
lines changed

2 files changed

+42
-39
lines changed

README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,19 @@ Use the following Target IDs (--targetId option) when running commands directly:
6767
| `Ledger Blue v2` | 2.1.x | `0x31010004` |
6868

6969

70+
## PCSC support
71+
72+
This package can optionally work with PCSC readers for NFC communication with Ledger devices
73+
74+
Installation instructions on linux:
75+
```
76+
apt install libpcsclite-dev
77+
pip3 install pyscard
78+
```
79+
80+
Usage:
81+
If the environment variable `PCSC=1` is defined, ledgerblue tools will communicate through the first PCSC interface with a detected NFC tag
82+
7083
## Ledgerblue documentation
7184

7285
You can generate the Ledgerblue documentation locally.

ledgerblue/comm.py

Lines changed: 29 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -64,16 +64,14 @@
6464
if "LEDGER_BLE_PROXY" in os.environ:
6565
BLE_PROXY = True
6666

67-
# Force use of MCUPROXY if required
6867
PCSC = None
6968
if "PCSC" in os.environ and len(os.environ["PCSC"]) != 0:
7069
PCSC = os.environ["PCSC"]
71-
if PCSC:
7270
try:
71+
# Don't force all users to install pyscard
7372
from smartcard.System import readers
74-
from smartcard.util import toBytes
7573
except ImportError:
76-
PCSC = False
74+
PCSC = None
7775

7876

7977
def get_possible_error_cause(sw):
@@ -268,11 +266,11 @@ def __init__(self, device, debug=False):
268266

269267
def exchange(self, apdu, timeout=TIMEOUT):
270268
if self.debug:
271-
print("SC => %s" % apdu.hex())
272-
response, sw1, sw2 = self.device.transmit(toBytes(hexlify(apdu)))
269+
print(f"[SC] => {apdu.hex()}")
270+
response, sw1, sw2 = self.device.transmit(list(apdu))
273271
sw = (sw1 << 8) | sw2
274272
if self.debug:
275-
print("SC <= %s%.2x" % (response.hex(), sw))
273+
print("[SC] <= %s%.2x" % (bytes(response).hex(), sw))
276274
if sw != 0x9000 and (sw & 0xFF00) != 0x6100 and (sw & 0xFF00) != 0x6C00:
277275
raise CommException("Invalid status %04x" % sw, sw, bytearray(response))
278276
return bytearray(response)
@@ -286,7 +284,7 @@ def close(self):
286284
self.opened = False
287285

288286

289-
def getDongle(debug=False, selectCommand=None):
287+
def getDongle(debug=False):
290288
if APDUGEN:
291289
return HIDDongleHIDAPI(None, True, debug)
292290

@@ -300,45 +298,37 @@ def getDongle(debug=False, selectCommand=None):
300298
return DongleNFC(debug)
301299
elif BLE_PROXY:
302300
return DongleBLE(debug)
303-
dev = None
304-
hidDevicePath = None
305-
ledger = True
306-
for hidDevice in hid.enumerate(0, 0):
307-
if hidDevice["vendor_id"] == 0x2C97:
308-
if (
309-
"interface_number" in hidDevice and hidDevice["interface_number"] == 0
310-
) or ("usage_page" in hidDevice and hidDevice["usage_page"] == 0xFFA0):
311-
hidDevicePath = hidDevice["path"]
312-
313-
usb_port = os.getenv("LEDGER_PROXY_USB_PORT")
314-
if usb_port:
315-
hidDevicePath = usb_port.encode()
316-
if hidDevicePath is not None:
317-
dev = hid.device()
318-
dev.open_path(hidDevicePath)
319-
dev.set_nonblocking(True)
320-
return HIDDongleHIDAPI(dev, ledger, debug)
321-
if PCSC:
301+
elif PCSC is not None:
302+
# Use the first pcsc reader with a card inserted
322303
connection = None
323304
for reader in readers():
324305
try:
325306
connection = reader.createConnection()
326307
connection.connect()
327-
if selectCommand is not None:
328-
response, sw1, sw2 = connection.transmit(
329-
toBytes("00A4040010FF4C4547522E57414C5430312E493031")
330-
)
331-
sw = (sw1 << 8) | sw2
332-
if sw == 0x9000:
333-
break
334-
else:
335-
connection.disconnect()
336-
connection = None
337-
else:
338-
break
339308
except Exception:
340309
connection = None
341310
pass
342311
if connection is not None:
343312
return DongleSmartcard(connection, debug)
313+
else:
314+
# USB HID by default
315+
dev = None
316+
hidDevicePath = None
317+
ledger = True
318+
for hidDevice in hid.enumerate(0, 0):
319+
if hidDevice["vendor_id"] == 0x2C97:
320+
if (
321+
"interface_number" in hidDevice and hidDevice["interface_number"] == 0
322+
) or ("usage_page" in hidDevice and hidDevice["usage_page"] == 0xFFA0):
323+
hidDevicePath = hidDevice["path"]
324+
325+
usb_port = os.getenv("LEDGER_PROXY_USB_PORT")
326+
if usb_port:
327+
hidDevicePath = usb_port.encode()
328+
if hidDevicePath is not None:
329+
dev = hid.device()
330+
dev.open_path(hidDevicePath)
331+
dev.set_nonblocking(True)
332+
return HIDDongleHIDAPI(dev, ledger, debug)
333+
344334
raise CommException("No dongle found")

0 commit comments

Comments
 (0)