Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to read a smart card/ microprocessor card using a smart card reader in android programmatically

So lately I have been working with smart cards that hold some information and what I am trying to achieve here is to fetch this data from these smart card using a smart card reader through any Android Smartphone. I have been using a HID OMNIKEY 3021 USB smart card reader which would read these card(And I know this reader works with these cards through windows apps because I have personally tested this)

Now Android provides USB Host that makes it possible to read any USB Host provided the Android Smartphones supports it.

And I am trying to use these classes provided by USB Host to reach out to the data inside this card.

My code to detect any USB Host :

private static final String ACTION_USB_PERMISSION = "com.android.example.USB_PERMISSION";
PendingIntent mPermissionIntent = PendingIntent.getBroadcast(this, 0, new Intent(ACTION_USB_PERMISSION), 0);

IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION);
registerReceiver(mUsbReceiver, filter);

IntentFilter attachedFilter = new IntentFilter(UsbManager.ACTION_USB_DEVICE_ATTACHED);
registerReceiver(mUsbAttachedReceiver, attachedFilter);

private final BroadcastReceiver mUsbAttachedReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        Utils.writeStringToTextFile("\n1 .Get an action : " + action, FileName);
        if (UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(action)) {
            synchronized (this) {
                device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
                if (device != null) {
                    showToast("Plugged In");
                    mUsbManager.requestPermission(device, mPermissionIntent);
                }
            }
        } else if (UsbManager.ACTION_USB_DEVICE_DETACHED.equals(action)) {
            UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
            if (device != null) {
                showToast("Plugged Out");
                // call your method that cleans up and closes communication with the device
            }
        }
    }
};

private final BroadcastReceiver mUsbReceiver = new BroadcastReceiver() {

    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        if (ACTION_USB_PERMISSION.equals(action)) {
            synchronized (this) {
                device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);

                if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
                    if (device != null) {
                        //call method to set up device communication
                        Utils.writeStringToTextFile("2 .Get an action : " + action + "\nDevice is : " + device, FileName);
                        showToast("Permission Granted for device");

                        Handler h = new Handler();
                        h.postDelayed(run, 1000);

                    }
                } else {
                    showToast("Permission denied for device" + device);
                }
            }
        }
    }
};

Everything works as expected as I get the UsbDevice device which gives out the info of device, example :

Device is : UsbDevice[mName=/dev/bus/usb/001/002,mVendorId=1899,mProductId=12322,mClass=0,mSubclass=0,mProtocol=0,mManufacturerName=OMNIKEY AG,mProductName=Smart Card Reader USB,mVersion=2.0,mSerialNumber=null,mConfigurations=[
UsbConfiguration[mId=1,mName=CCID,mAttributes=160,mMaxPower=50,mInterfaces=[
UsbInterface[mId=0,mAlternateSetting=0,mName=null,mClass=11,mSubclass=0,mProtocol=0,mEndpoints=[
UsbEndpoint[mAddress=131,mAttributes=3,mMaxPacketSize=8,mInterval=24]
UsbEndpoint[mAddress=132,mAttributes=2,mMaxPacketSize=64,mInterval=0]
UsbEndpoint[mAddress=5,mAttributes=2,mMaxPacketSize=64,mInterval=0]]]]

Now I am trying to use this UsbDevice device to fetch data and details from the card but I am not successful in doing so and I couldn't find any helpful post regarding this.

I know I have to use UsbInterface, UsbEndpoint, UsbDeviceConnection to get the things I want from the card but I am unable to do so.

Also, I am not able to find any samples or such thing for the same. Can anyone point me to the right direction?

Sorry for the long post also thanks in Advance :)

EDIT : Thanks to Mr. Michael Roland, I was able to read about CCID as reader device speaks CCID over the USB interface.

So I used the following code :

        UsbDeviceConnection connection = mUsbManager.openDevice(device);
        UsbEndpoint epOut = null, epIn = null;

        for (int i = 0; i < device.getInterfaceCount(); i++) {
            UsbInterface usbInterface = device.getInterface(i);
            connection.claimInterface(usbInterface, true);

            for (int j = 0; j < usbInterface.getEndpointCount(); j++) {
                UsbEndpoint ep = usbInterface.getEndpoint(j);
                showToast("Endpoint is : " + ep.toString() + " endpoint's type : " + ep.getType() + " endpoint's direction : " + ep.getDirection());
                Log.d(" ", "EP " + i + ": " + ep.getType());
                if (ep.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK) {
                    if (ep.getDirection() == UsbConstants.USB_DIR_OUT) {
                        epOut = ep;

                    } else if (ep.getDirection() == UsbConstants.USB_DIR_IN) {
                        epIn = ep;
                    }

                }
            }

            int dataTransferred = 0;
            byte[] PC_to_RDR_IccPowerOn = hexStringToByteArray("62" + "00000000" + "00" + "00" + "00" + "0000");

            if (epOut != null) {
                //Firstly send Power in on Bulk OUT endpoint
                dataTransferred = connection.bulkTransfer(epOut, PC_to_RDR_IccPowerOn, PC_to_RDR_IccPowerOn.length, TIMEOUT);
            }

            StringBuilder result = new StringBuilder();

            if (epIn != null) {
                final byte[] RDR_to_PC_DataBlock = new byte[epIn.getMaxPacketSize()];
                result = new StringBuilder();
                //Secondly send Power out on Bulk OUT endpoint
                dataTransferred = connection.bulkTransfer(epIn, RDR_to_PC_DataBlock, RDR_to_PC_DataBlock.length, TIMEOUT);
                for (byte bb : RDR_to_PC_DataBlock) {
                    result.append(String.format(" %02X ", bb));
                }

                if (dataTransferred > 0) {
                    Utils.writeStringToTextFile("\n2nd buffer received was : " + result.toString(), "Card_communication_data.txt");
                    String s1 = Arrays.toString(RDR_to_PC_DataBlock);
                    String s2 = new String(RDR_to_PC_DataBlock);
                    showToast("received - " + s1 + " - " + s2);
                } else {
                    showToast("received length at 2nd buffer transfer was " + dataTransferred);
                }
            }
        }

And I received 80 13 00 00 00 00 00 00 00 00 3B 9A 96 C0 10 31 FE 5D 00 64 05 7B 01 02 31 80 90 00 76 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 But still I am not sure what to do with Data field: ATR or how to form Command APDU for PC_to_RDR_XfrBlock command..

I think I am supposed to

Send command APDU wrapped into PC_to_RDR_XfrBlock command

now; can anyone help me with this?

EDIT 2: I figured out what ATR means and How to form command APDU.

But now I am supposed to switch protocol

The default Protocol is T=0. To set the T=1 protocol, a PTS (also known as PPS) must be sent to the card by the device As both T=0 and T=1 protocols are mandatory for the card, the basic PTS for protocol switching is mandatory for the card. The PTS can be used, as indicated in ISO/IEC 7816-3, to switch to higher baud rates than the default one proposed by the card in the ATR if any (TA(1) byte).

And I am not sure what this means and How to achieve this!!

like image 209
shadygoneinsane Avatar asked Jul 14 '17 09:07

shadygoneinsane


2 Answers

Since there is no proper guide or any sample that lays out the basic steps that one can follow, so here is how I managed to communicate (this is more like a noobs' guide and please correct me if I am wrong): Firstly Using the USB Host API I was able to connect to the smartcard through a smartcard reader.

For connection, here is a snippet to help you understand:

    //Allows you to enumerate and communicate with connected USB devices.
    UsbManager mUsbManager = (UsbManager) getSystemService(Context.USB_SERVICE);
    //Explicitly asking for permission
    final String ACTION_USB_PERMISSION = "com.android.example.USB_PERMISSION";
    PendingIntent mPermissionIntent = PendingIntent.getBroadcast(this, 0, new Intent(ACTION_USB_PERMISSION), 0);
    HashMap<String, UsbDevice> deviceList = mUsbManager.getDeviceList();

    UsbDevice device = deviceList.get("//the device you want to work with");
    if (device != null) {
        mUsbManager.requestPermission(device, mPermissionIntent);
    }

Now you have to understand that in java the communication takes place using package javax.smarcard which is not available for Android so take a look here for getting an idea as to how you can communicate or send/receive APDU (smartcard command).

Now as told in the answer mentioned above

You cannot simply send an APDU (smartcard command) over the bulk-out endpoint and expect to receive a response APDU over the bulk-in endpoint.

For getting the endpoints see the code snippet below :

UsbEndpoint epOut = null, epIn = null;
UsbInterface usbInterface;

UsbDeviceConnection connection = mUsbManager.openDevice(device);

        for (int i = 0; i < device.getInterfaceCount(); i++) {
            usbInterface = device.getInterface(i);
            connection.claimInterface(usbInterface, true);

            for (int j = 0; j < usbInterface.getEndpointCount(); j++) {
                UsbEndpoint ep = usbInterface.getEndpoint(j);

                if (ep.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK) {
                    if (ep.getDirection() == UsbConstants.USB_DIR_OUT) {
                        // from host to device
                        epOut = ep;

                    } else if (ep.getDirection() == UsbConstants.USB_DIR_IN) {
                        // from device to host
                        epIn = ep;
                    }
                }
            }
        }

Now you have the bulk-in and bulk-out endpoints to send and receive APDU command and APDU response blocks:

For sending commands, see the code snippet below:

public void write(UsbDeviceConnection connection, UsbEndpoint epOut, byte[] command) {
    result = new StringBuilder();
    connection.bulkTransfer(epOut, command, command.length, TIMEOUT);
    //For Printing logs you can use result variable
    for (byte bb : command) {
        result.append(String.format(" %02X ", bb));
    }
}

And for receive/ read a response see the code snippet below :

public int read(UsbDeviceConnection connection, UsbEndpoint epIn) {
result = new StringBuilder();
final byte[] buffer = new byte[epIn.getMaxPacketSize()];
int byteCount = 0;
byteCount = connection.bulkTransfer(epIn, buffer, buffer.length, TIMEOUT);

    //For Printing logs you can use result variable
    if (byteCount >= 0) {
        for (byte bb : buffer) {
            result.append(String.format(" %02X ", bb));
        }

        //Buffer received was : result.toString()
    } else {
        //Something went wrong as count was : " + byteCount
    }

    return byteCount;
}

Now if you see this answer here the 1st command to be sent is :

PC_to_RDR_IccPowerOn command to activate the card.

which you can create by reading section 6.1.1 of the USB Device Class Specifications doc here.

Now let's take an example of this command like the one here: 62000000000000000000 How you can send this is :

write(connection, epOut, "62000000000000000000");

Now after you have successfully sent the APDU command, you can read the response using :

read(connection, epIn);

And receive something like

80 18000000 00 00 00 00 00 3BBF11008131FE45455041000000000000000000000000F1

Now the response received in the code here will be in the result variable of read() method from code above which you can use for getting the Data field: ATR from the same RDR_to_PC_DataBlock

You have to know/read about how to read this ATR which can be used to detect the type of card and other things like whether it's using T0 or T1 protocol then whats TA(1) which can tell about the FI Index into clock conversion factor table and DI Index into Baud rate adjustment factor table and so on

Check this website for parsing of ATR.

Now If you want to switch over protocol say T0 to T1 i.e send PPS/PTS or you want to set any parameter, then you can use PC_to_RDR_SetParameters command(section 6.1.7 of the document).

Example of PPS/PTS for switching from T0 to T1 in my case was : "61 00000007 00 00 01 0000 9610005D00FE00" .

which will give out some result like : "82 07000000 00 00 00 00 01 96 10 00 5D 00 FE 00". which you check with RDR_to_PC_Parameters(section 6.2.3 of the document)

For details of this command see sec 6.1.7 of the CCID protocol Document. I was able to form this command using details received from ATR block and then send the command using the write() method and then read the response using read() method.

Also for selecting any file or send any command you can use the PC_to_RDR_XfrBlock to send using write() method and then receive the response using read() method from code. Also you can change the no of btyes you want to read in read() method.

Remember to use threads for communicating and also read here for more tips.

like image 105
shadygoneinsane Avatar answered Oct 03 '22 21:10

shadygoneinsane


Typical USB smartcard readers implement the USB CCID device class specification. Consequently, you need to implement that protocol in your application in order to communicate with the reader (and the card). See Communicate with smartcard reader through Android USB host for a (partially working) starting point on how to implement that.

like image 42
Michael Roland Avatar answered Oct 03 '22 21:10

Michael Roland