Monday, March 31, 2014

Host Card Emulation Series: Card Agents and APDUs

In Android 4.4 to support HCE, HostApduService declares processCommandApdu() method that the developer needs to implement. The method processCommandApdu() is called when the service receives an Application Protocol Data Unit (APDU) sent by contactless/NFC reader. The developer needs to implement how to process APDUs specific to the card application(s) the service supports. The implementation can become complex if the service supports multiple card applications.

SimplyTapp SDK provides a robust architecture where each card application is implemented in a "card agent". Using SimplyTapp SDK, there is no need for the developer to implement a complex processCommandApdu() method for a service that supports multiple card applications. Instead the developer implements process() method in the card agent for each card application. A mobile application can support multiple card agents and card agents can be remotely deployed to the mobile application, similar to remotely deploying Java Card applet (aka cardlet) to the Secure Element. Essentially SimplyTapp SDK provides an architecture that is familiar to Java Card developers.

I will describe how the card agent processes different APDU cases as defined in ISO/IEC 7816-4 specification.
ISO 7816-4 Case 1 No Lc
No Le
Example: 80100000
ISO 7816-4 Case 2 No Lc
1-byte Le (1 to 256, Le=0x00 represents 256)
Example: 8012000000
ISO 7816-4 Case 3 1-byte Lc (1 to 255)
No Le
Example: 80140000081112131415161718
ISO 7816-4 Case 4 1-byte Lc (1 to 255)
1-byte Le (1 to 256, Le=0x00 represents 256)
Example: 8016000008212223242526272800

Here is sample code for card agent implementation that demonstrates how to process the example APDUs. The class extends com.simplytapp.virtualcard.Agent.
 import javacard.framework.APDU;  
 import javacard.framework.ISO7816;  
 import javacard.framework.ISOException;  
   
 import com.simplytapp.virtualcard.Agent;  
   
 public class CardAgent extends Agent {  
   
   public void process(APDU apdu) throws ISOException {  
     byte[] apduBuffer = apdu.getBuffer();  
     byte claByte = apduBuffer[ISO7816.OFFSET_CLA];  
     byte insByte = apduBuffer[ISO7816.OFFSET_INS];  
   
     if (claByte == (byte) 0x80) {  
       if (insByte == (byte) 0x10) {  
         // Process case 1 command APDU.  
         processCase1Apdu();  
   
         // Return SW=0x9000.  
       }  
       else if (insByte == (byte) 0x12) {  
         // Process case 2 command APDU.  
   
         // Copy response data to APDU buffer.  
         System.arraycopy(responseData, 0,   
             apduBuffer, 0, responseData.length);  
   
         short le = apdu.setOutgoing();  
         if (responseData.length > le) {  
           le = (short) responseData.length;  
         }  
         apdu.setOutgoingLength(le);  
         apdu.sendBytes((short) 0, le);  
         // Return response data with SW=0x9000.  
       }  
       else if (insByte == (byte) 0x14) {  
         // Process case 3 command APDU.  
         short lc = apdu.setIncomingAndReceive();  
   
         // Save command data from APDU buffer.  
         System.arraycopy(apduBuffer, ISO7816.OFFSET_CDATA,   
             commandData, 0, lc);  
   
         // Return SW=0x9000.  
       }  
       else if (insByte == (byte) 0x16) {  
         // Process case 4 command APDU.  
         short lc = apdu.setIncomingAndReceive();  
   
         // Save command data from APDU buffer.  
         System.arraycopy(apduBuffer, ISO7816.OFFSET_CDATA,   
             commandData, 0, lc);  
   
         // Copy response data to APDU buffer.  
         System.arraycopy(responseData, 0,   
             apduBuffer, 0, responseData.length);  
   
         short le = apdu.setOutgoing();  
         if (responseData.length > le) {  
           le = (short) responseData.length;  
         }  
         apdu.setOutgoingLength(le);  
         apdu.sendBytes((short) 0, le);  
         // Return response data with SW=0x9000.  
       }  
     }  
   }  
   
 }  

The card agent can also process extended length APDUs as defined in ISO/IEC 7816-4 specification. The maximum supported length using extended length is 32767 to be consistent with Java Card API.
ISO 7816-4 Case 1 Not Applicable
ISO 7816-4 Case 2 No Lc
3-byte Le (1 to 32767, Le=0x000000 represents 32767)
Example: 80120000000000
ISO 7816-4 Case 3 3-byte Lc (1 to 32767)
No Le
Example: 801400000000081112131415161718
ISO 7816-4 Case 4 3-byte Lc (1 to 32767)
2-byte Le (1 to 32767, Le=0x0000 represents 32767)
Example: 8016000000000821222324252627280000

Here is sample code for card agent implementation that demonstrates how to process the example extended length APDUs. The class extends com.simplytapp.virtualcard.Agent and implements javacardx.apdu.ExtendedLength.
 import javacard.framework.APDU;  
 import javacard.framework.ISO7816;  
 import javacard.framework.ISOException;  
 import javacardx.apdu.ExtendedLength;  
   
 import com.simplytapp.virtualcard.Agent;  
   
 public class CardAgent extends Agent implements ExtendedLength {  
   
   public void process(APDU apdu) throws ISOException {  
     byte[] apduBuffer = apdu.getBuffer();  
     byte claByte = apduBuffer[ISO7816.OFFSET_CLA];  
     byte insByte = apduBuffer[ISO7816.OFFSET_INS];  
   
     if (claByte == (byte) 0x80) {  
       if (insByte == (byte) 0x12) {  
         // Process case 2 command APDU.  
   
         // Copy response data to APDU buffer.  
         System.arraycopy(responseData, 0,   
             apduBuffer, 0, responseData.length);  
   
         short le = apdu.setOutgoing();  
         if (responseData.length > le) {  
           le = (short) responseData.length;  
         }  
         apdu.setOutgoingLength(le);  
         apdu.sendBytes((short) 0, le);  
         // Return response data with SW=0x9000.  
       }  
       else if (insByte == (byte) 0x14) {  
         // Process case 3 command APDU.  
         short receivedLen = apdu.setIncomingAndReceive();  
   
         // Save command data from APDU buffer.  
         System.arraycopy(apduBuffer, apdu.getOffsetCdata(),   
             commandData, 0, receivedLen);  
   
         short lc = apdu.getIncomingLength();  
         // Check if additional command data not yet received.  
         if (receivedLen != lc) {  
           short commandDataOffset = receivedLen;  
   
           // Receive more command data until no more available.  
           receivedLen = apdu.receiveBytes((short) 0);  
           while (receivedLen != 0) {  
             // Save additional command data from APDU buffer.  
             System.arraycopy(apduBuffer, 0,   
                 commandData, commandDataOffset, receivedLen);  
             commandDataOffset += receivedLen;  
             receivedLen = apdu.receiveBytes((short) 0);  
           }  
         }  
   
         // Return SW=0x9000.  
       }  
       else if (insByte == (byte) 0x16) {  
         // Process case 4 command APDU.  
         short receivedLen = apdu.setIncomingAndReceive();  
   
         // Save command data from APDU buffer.  
         System.arraycopy(apduBuffer, apdu.getOffsetCdata(),   
             commandData, 0, receivedLen);  
   
         short lc = apdu.getIncomingLength();  
         // Check if additional command data not yet received.  
         if (receivedLen != lc) {  
           short commandDataOffset = receivedLen;  
   
           // Receive more command data until no more available.  
           receivedLen = apdu.receiveBytes((short) 0);  
           while (receivedLen != 0) {  
             // Save additional command data from APDU buffer.  
             System.arraycopy(apduBuffer, 0,   
                 commandData, commandDataOffset, receivedLen);  
             commandDataOffset += receivedLen;  
             receivedLen = apdu.receiveBytes((short) 0);  
           }  
         }  
   
         // Copy response data to APDU buffer.  
         System.arraycopy(responseData, 0,   
             apduBuffer, 0, responseData.length);  
   
         short le = apdu.setOutgoing();  
         if (responseData.length > le) {  
           le = (short) responseData.length;  
         }  
         apdu.setOutgoingLength(le);  
         apdu.sendBytes((short) 0, le);  
         // Return response data with SW=0x9000.  
       }  
     }  
   }  
   
 }  

3 comments:

  1. Hi Ming, thanks for the article explaining about card agents. I have created an app which loads card from the SimplyTapp server as given here http://blog.simplytapp.com/2014/10/the-absolute-simplest-hce-application.html . Now I want to read card details such as Ppse, Card Aid, Processing options, Swipe data from VirtualCard object loaded from the server. Can you please help me how I can achieve this, I would be grateful for your help.

    Thanks,
    Pradeep.

    ReplyDelete
  2. Hi Ming, thanks for the blog on HCE. I am using SimplyTapp sdk as given in http://blog.simplytapp.com/2014/10/the-absolute-simplest-hce-application.html . But am facing 2 issues, one is a crash whose stacktrace is given below

    W/dalvikvm( 9324): threadid=17: thread exiting with uncaught exception (group=0x41efec08)
    D/MainActivity( 9324): Account is Disabled cardId=6164 code=24
    D/MainActivity( 9324): Account is Disabled cardId=6164 code=24
    E/AndroidRuntime( 9324): FATAL EXCEPTION: Thread-2714
    E/AndroidRuntime( 9324): Process: com.example.sampletapp, PID: 9324
    E/AndroidRuntime( 9324): java.lang.NullPointerException
    E/AndroidRuntime( 9324): at com.simplytapp.cardagent.c.run(SourceFile:1794)
    E/AndroidRuntime( 9324): at java.lang.Thread.run(Thread.java:841)
    W/ActivityManager( 3042): Force finishing activity com.example.sampletapp/.MainActivity
    I/CardAgent( 9324): activated, tGetAccountParams is still accessing remote card applet, waiting...
    I/ServiceKeeper( 3042): In getseinfo pid = 3042 uid = 1000 seinfo= system
    D/CrashAnrDetector( 3042): processName: com.example.sampletapp
    D/CrashAnrDetector( 3042): broadcastEvent : com.example.sampletapp data_app_crash
    W/ApplicationPackageManager( 3042): getCSCPackageItemText()
    V/SmartFaceService - 3rd party pause( 3042): onReceive [android.intent.action.ACTIVITY_STATE/com.example.sampletapp/pause]
    D/SSRMv2:CustomFrequencyManagerService( 3042): acquireDVFSLockLocked : type : DVFS_MIN_LIMIT frequency : 1200000 uid : 1000 pid : 3042 pkgName : ACTIVITY_RESUME_BOOSTER@4
    W/ActivityManager( 3042): mDVFSHelper.acquire()

    In another issue, am trying you read card data using SoftPcd as below but transceiveWithCard() function is returning same value 6F00 in all the cases. Your help would be greatly appreciated.
    SoftPcd softPcd = new SoftPcd((short)5000);
    try {
    virtualCard.transactWithSoftPcd(softPcd);
    } catch (IOException e) {
    e.printStackTrace();
    }
    try {
    softPcd.connect();
    byte[] apdu = softPcd.transceiveWithCard(new byte[]{0x00,(byte)0xA4,0x04,0x00,0x05,(byte)0x32,0x50,0x41,0x59,(byte)0x2E});
    apdu = softPcd.transceiveWithCard(new byte[]{0x00,(byte)0xA4,0x04,0x00,0x07,(byte)0xA0,0x00,0x00,0x02,0x77,0x10,0x10,0x00});
    apdu = softPcd.transceiveWithCard(new byte[]{(byte)0x80,(byte)0xA8,0x00,0x00,0x15,(byte)0x83,0x13,(byte)0xD0,(byte)0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
    0x01,0x01,0x24,0x01,0x24,0x02,0x66,0x33,(byte)0x82,0x01,0x00});
    apdu = softPcd.transceiveWithCard(new byte[]{0x00,(byte)0xB2,0x01,0x0C,0x00});
    apdu = softPcd.transceiveWithCard(new byte[]{0x00,(byte)0xB2,0x01,0x14,0x00});
    apdu = softPcd.transceiveWithCard(new byte[]{0x00,(byte)0xB2,0x02,0x14,0x00});
    apdu = softPcd.transceiveWithCard(new byte[]{0x00,(byte)0xB2,0x03,0x14,0x00});
    apdu = softPcd.transceiveWithCard(new byte[]{0x00,(byte)0xB2,0x04,0x14,0x00});
    apdu = softPcd.transceiveWithCard(new byte[]{0x00,(byte)0xB2,0x01,0x1C,0x00});
    apdu = softPcd.transceiveWithCard(new byte[]{(byte)0x80,(byte)0xAE,(byte)0x80,0x00,0x2A,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x24,
    0x00,(byte)0x80,0x00,(byte)0x80,0x00,0x01,0x24,0x13,0x06,0x27,0x00,0x02,0x66,0x33,(byte)0x82,0x00,0x00,0x00,0x00,
    0x00,0x00,0x00,0x00,0x00,0x00,0x1F,0x00,0x00,0x00});
    softPcd.disconnect();
    } catch (IOException e) {
    e.printStackTrace();
    }

    Thanks,
    Pradeep.

    ReplyDelete