Sometimes you need a way to identify which USB device is the smart reader reported by PC/SC.
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:
- SCARD_ATTR_CHANNEL_ID
DWORD encoded as 0xDDDDCCCC, where DDDD = data channel type and CCCC = channel number:
- 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.
So for a USB device the DDDD value is 0x0020 and we have 2 bytes for the CCCC value.
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 using
CM_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?