That was the request of Stephan Guilloux in issue 68 and Pull Request 69.
SCARD_ATTR_CHANNEL_ID
The WinSCard API already provides something like that with the SCARD_ATTR_CHANNEL_ID attribute.The Microsoft documentation says:
So for a USB device the DDDD value is 0x0020 and we have 2 bytes for the CCCC value.DWORD encoded as 0xDDDDCCCC, where DDDD = data channel type and CCCC = channel number:
- SCARD_ATTR_CHANNEL_ID
- The following encodings are defined for DDDD:
- 0x01 serial I/O; CCCC is a port number.
- 0x02 parallel I/O; CCCC is a port number.
- 0x04 PS/2 keyboard port; CCCC is zero.
- 0x08 SCSI; CCCC is SCSI ID number.
- 0x10 IDE; CCCC is device number.
- 0x20 USB; CCCC is device number.
- 0xFy vendor-defined interface with y in the range zero through 15; CCCC is vendor defined.
We decided to use the 2 bytes to encode the bus number in the most significant byte (MSB) and the device address in the least significant byte (LSB).
Implementation
I added the support of this new attribute in the CCID driver version 1.4.32.I also added support in the PCSC Unitary Test SCardGetAttrib.py to check the code is working correctly.
Example (Low Level Python API)
#! /usr/bin/env python from smartcard.scard import * from smartcard.pcsc.PCSCExceptions import * from struct import unpack hresult, hcontext = SCardEstablishContext(SCARD_SCOPE_USER) if hresult != SCARD_S_SUCCESS: raise EstablishContextException(hresult) hresult, readers = SCardListReaders(hcontext, []) if hresult != SCARD_S_SUCCESS: raise ListReadersException(hresult) for reader in readers: hresult, hcard, dwActiveProtocol = SCardConnect(hcontext, reader, SCARD_SHARE_DIRECT, SCARD_PROTOCOL_ANY) if hresult != SCARD_S_SUCCESS: raise BaseSCardException(hresult) print("reader:", reader) hresult, attrib = SCardGetAttrib(hcard, SCARD_ATTR_CHANNEL_ID) if hresult != SCARD_S_SUCCESS: print(SCardGetErrorMessage(hresult)) else: print(attrib) # get the DWORD value DDDDCCCC = unpack("i", bytearray(attrib))[0] DDDD = DDDDCCCC >> 16 if DDDD == 0x0020: bus = (DDDDCCCC & 0xFF00) >> 8 addr = DDDDCCCC & 0xFF print(" USB: bus: {}, addr: {}".format(bus, addr)) hresult = SCardDisconnect(hcard, SCARD_LEAVE_CARD) if hresult != SCARD_S_SUCCESS: raise BaseSCardException(hresult) hresult = SCardReleaseContext(hcontext) if hresult != SCARD_S_SUCCESS: raise ReleaseContextException(hresult)
The important part of the code is the line:
DDDDCCCC = unpack("i", bytearray(attrib))[0]As documented by Microsoft, the SCARD_ATTR_CHANNEL_ID attribute does not return a buffer of bytes but a DWORD.
The difference is that the order of the 4 bytes of the DWORD is important and depends on the CPU architecture. The code must take into account the endianess of the CPU. Since the Python program does not know if the CPU is little or big endian we must use
unpack()
to let the CPU decode the 4 bytes as an integer using its internal endianess.If you have a better solution please share it.
Output
$ python3 SCardGetAttrib.py reader: Gemalto PC Twin Reader 00 00 [8, 1, 32, 0] USB: bus: 1, addr: 8 reader: Cherry KC 1000 SC Z [KC 1000 SC Z] 01 00 [5, 1, 32, 0] USB: bus: 1, addr: 5
Example (high level Python API)
We can also use the higher level Python API.The code is much shorter but may be more complex to understand and translate into a PC/SC wrapper in another language.
#! /usr/bin/env python from smartcard.System import readers from smartcard.scard import (SCARD_SHARE_DIRECT, SCARD_ATTR_CHANNEL_ID) from struct import unpack for reader in readers(): print("reader:", reader) card_connection = reader.createConnection() card_connection.connect(mode=SCARD_SHARE_DIRECT) attrib = card_connection.getAttrib(SCARD_ATTR_CHANNEL_ID) print(attrib) DDDDCCCC = unpack("i", bytearray(attrib))[0] DDDD = DDDDCCCC >> 16 if DDDD == 0x0020: bus = (DDDDCCCC & 0xFF00) >> 8 addr = DDDDCCCC & 0xFF print(" USB: bus: {}, addr: {}".format(bus, addr))
Output
Of course the output is the same.$ python3 getAttrib.py reader: Gemalto PC Twin Reader 00 00 [8, 1, 32, 0] USB: bus: 1, addr: 8 reader: Cherry KC 1000 SC Z [KC 1000 SC Z] 01 00 [5, 1, 32, 0] USB: bus: 1, addr: 5
Use case
The use case of this new feature is be able to make a bijective relation between a PC/SC smart card reader and a USB device connected to the host.The information returned by
SCARD_ATTR_CHANNEL_ID
is also returned by the lsusb command (on GNU/Linux).$ lsusb Bus 002 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub Bus 001 Device 006: ID 1bcf:0005 Sunplus Innovation Technology Inc. Optical Mouse Bus 001 Device 005: ID 046a:00a4 Cherry GmbH Bus 001 Device 008: ID 08e6:3437 Gemalto (was Gemplus) GemPC Twin SmartCard Reader Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
You can see that the PC/SC reader "Gemalto PC Twin Reader 00 00" is at "USB: bus: 1, addr: 8" for PC/SC and at "Bus 001 Device 008:" for lsusb.
You can also ask lsusb to list only this specific device using:
$ lsusb -s 1:8 Bus 001 Device 008: ID 08e6:3437 Gemalto (was Gemplus) GemPC Twin SmartCard Reader
You may have noticed that the device "Bus 001 Device 005" is the PC/SC reader "Cherry KC 1000 SC Z [KC 1000 SC Z] 01 00" and it is also my keyboard. It is a Cherry KC 1000 SC Z.
More details
You can also get more information about the USB reader at the PC/SC level usingCM_IOCTL_GET_FEATURE_REQUEST
to get PCSCv2_PART10_PROPERTY_wIdVendor
and PCSCv2_PART10_PROPERTY_wIdProduct
values.See "Identifying a reader model (part 2)".
Conclusion
Not everybody will need to use this new feature.But it was easy to implement and it has no side effect. So why not?