Wednesday, October 5, 2011

FEATURE_CCID_ESC_COMMAND

Since revision 5991 my CCID driver does support FEATURE_CCID_ESC_COMMAND and bPPDUSupport.

These two services have been introduced in PC/SC v2 part 10 version 2.02.07 March 2010 and 2.02.08 April 2010.

FEATURE_CCID_ESC_COMMAND

This feature can be used to retrieve the control code to send a CCID escape command (PC_to_RDR_Escape) to the reader using SCardControl().

The value is returned by the CM_IOCTL_GET_FEATURE_REQUEST command. This will return a TLV data stream. The dwControlCode to use is the value corresponding to the tag FEATURE_CCID_ESC_COMMAND (0x13).

Before this mechanism it was possible to use the value IOCTL_SMARTCARD_VENDOR_IFD_EXCHANGE but this value is not specified/documented in any official document (AFAIK).

Using FEATURE_CCID_ESC_COMMAND is now portable (everywhere the PC/SC drivers implement it).

bPPDUSupport

The SCardControl() function is quiet limited with the Microsoft CCID driver on Windows. So some smart card reader manufacturers use SCardTransmit() to send commands to the reader (instead of the card).

This mechanism is fragile since the reader must interpret the command and determine if the command if for itself or for the card.

The PC/SC workgroup documented a way to know how the driver is expecting to receive commands for itself.

bPPDUSupport is a tag in the TLV data stream returned by FEATURE_GET_TLV_PROPERTIES. The value is coded as:
  • Bit 0: If set to 1, PPDU is supported over SCardControl() using FEATURE_CCID_ESC_COMMAND
  • Bit 1: If set to 1, PPDU is supported over SCardTransmit()

Implementation in my CCID driver

By default, for security reasons, sending an arbitrary PC_to_RDR_Escape command is forbidden. This can be configured using the ifdDriverOptions setting in the driver Info.plist file.


[...]
    <key>ifdDriverOptions</key>
    <string>0x0000</string>

    <!-- Possible values for ifdDriverOptions
    1: DRIVER_OPTION_CCID_EXCHANGE_AUTHORIZED
        the CCID Exchange command is allowed. You can use it through
        SCardControl(hCard, IOCTL_SMARTCARD_VENDOR_IFD_EXCHANGE, ...)

[...]
If the bit 0 of ifdDriverOptions is set then the driver will allow the use of IOCTL_SMARTCARD_VENDOR_IFD_EXCHANGE and now also the value associated to FEATURE_CCID_ESC_COMMAND

The driver will always report bPPDUSupport. By default the value is 0x00. But if If the bit 0 of ifdDriverOptions is set then the value of bPPDUSupport will be 0x01 indicating the support of FEATURE_CCID_ESC_COMMAND using dwControlCode and SCardControl().

Sample code

The use of these features may be hard to understand. So a small example can help.

In Python

#! /usr/bin/env python

"""
#   FEATURE_CCID_ESC_COMMAND.py: Unitary test for FEATURE_CCID_ESC_COMMAND
#   Copyright (C) 2011  Ludovic Rousseau
"""

#   This program is free software; you can redistribute it and/or modify
#   it under the terms of the GNU General Public License as published by
#   the Free Software Foundation; either version 3 of the License, or
#   (at your option) any later version.
#
#   This program is distributed in the hope that it will be useful,
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#   GNU General Public License for more details.
#
#   You should have received a copy of the GNU General Public License along
#   with this program; if not, see <http://www.gnu.org/licenses/>.

from smartcard.System import readers
from smartcard.pcsc.PCSCPart10 import (SCARD_SHARE_DIRECT,
    SCARD_LEAVE_CARD, FEATURE_CCID_ESC_COMMAND, getFeatureRequest, hasFeature)


def main():
    """ main """
    card_connection = readers()[0].createConnection()
    card_connection.connect(mode=SCARD_SHARE_DIRECT,
        disposition=SCARD_LEAVE_CARD)

    feature_list = getFeatureRequest(card_connection)

    ccid_esc_command = hasFeature(feature_list, FEATURE_CCID_ESC_COMMAND)
    if ccid_esc_command is None:
        raise Exception("The reader does not support FEATURE_CCID_ESC_COMMAND")

    # proprietary commands for Xiring readers
    version = [ord(c) for c in "VERSION"]
    res = card_connection.control(ccid_esc_command, version)
    print res
    print ''.join([chr(x) for x in res])

    serial = [ord(c) for c in "GET_SN"]
    res = card_connection.control(ccid_esc_command, serial)
    print res
    print ''.join([chr(x) for x in res])

if __name__ == "__main__":
    main()

In C

The C language is much more verbose than Python. I added support of FEATURE_CCID_ESC_COMMAND and bPPDUSupport in scardcontrol.c example code provided with the CCID driver.

The idea is the same as in Python:
  1. get the control code to use using FEATURE_CCID_ESC_COMMAND
  2. use SCardControl() to send a command
The C source code is 817 lines long. It is available online.

Comments

PC/SC now documents a way to send arbitrary proprietary commands to a reader. We now have two questions:

What command should you send?

You should know what you are doing and use the smart card reader user manual. So the command you want to use is documented, but maybe the documentation is not public. In general proprietary commands are not publicly documented.

How to know what reader exactly you are using?

I do not have an answer to this question. Using the PC/SC reader name is fragile. See a previous blog article What is in a PC/SC reader name? The reader name can change over time like with "Gemplus" becoming "Gemalto".

Maybe PC/SC must provide a new mechanism to report the USB VendorID and ProductID so that an application can be sure a specific reader is used.

Conclusion

Having to use proprietary commands is a sign of failure from the PC/SC workgroup. A service is needed but missing from the PC/SC standard. It has then been implemented using a proprietary mechanism (not documented, not portable, not interoperable).

It is a failure because the application is now linked to a particular smart card reader model.