SEEK adaptations in RIL for Galaxy S3

-- updated by Michael Roland on 2013-09-04 17:04 +0200

The project seek-for-android aims to add APIs for Android to communicate with secure elements regardless of their form-factor (e.g. UICC, SD card or embedded SE). The SEEK team published patches for AOSP that influence the telephony framework, the ASSD kernel and the NFC framework. Based on those changes the SmartCard API gives developers the possibility to develop security feature enhanced applications that make use of a secure element.

Samsung included SEEK with support for the UICC and the embedded SE on their Galaxy S3. As a developer you can easily write applications which communicate with the secure element on the UICC or the embedded SE. Unfortunately you can not use the publicly available patch from SEEK to build your own ROM with the SmartCard API and UICC support. While SEEK followed an approach to implement a standardized way to access the UICC through the phone's RIL, Samsung decided to use their own proprietary method for communicating with the UICC-based secure element through the RIL. Therefore, the SEEK implementation for accessing the UICC through the RIL won't work on Samsung's devices.

So, if you want to create your customized ROM for the Galaxy S3 that has SEEK with UICC-access, you have to adapt your ROM's telephony framework to send Samsung's proprietary command strucutres to the RIL. The main difference in Samsung's implementation is that they use OEM_HOOK_RAW requests instead of introducing new commands (SIM_*) for secure element communication. This means you have to adapt the three commands iccOpenChannel, iccCloseChannel and iccExchangeAPDU (which are added by SEEK's UICC patches) in your telephony framework (frameworks/opt/telephony). You can either change the methods in RIL.java or even better overwrite them in the Galaxy S3 specific implementation – SamsungExynos4RIL.java.

Let's start with the adaptions for iccOpenChannel: The main difference of the S3 is that the Samsung RILD implementation does not use specific RIL_REQUEST_SIM_* commands for the secure element access. Instead, you have to use the RIL_REQUEST_OEM_HOOK_RAW request to encapsulate the commands. From what we found, the format of these vendor-specific commands looks like this:

[command class (1 byte)] || [command (1 byte)] || [command length (2 bytes)] || [data (N bytes)]
  • command class always has the value 21
  • command is a 1-byte integer identifying the type of request:
    • 9 for open channel
    • 10 for close channel
    • 11 for sending an APDU
    • 12 for sending a Case-1 APDU command (no data and no expected response)
  • command length is a 2-byte integer (MSB first) that contains the length of the whole command including the data field: 4 + N

This means for the iccOpenChannel command we will create a byte array with the values:

> [21] [9] [4 + AID.length] [AID]

In the argument tag of the result parameter we also want to add some information about the type of command so that we are later able to properly decode the response message format. For that purpose we added three constant values to the class:

private static final int EVENT_EXCHANGE_APDU_DONE = 1;
private static final int EVENT_OPEN_CHANNEL_DONE  = 2;
private static final int EVENT_CLOSE_CHANNEL_DONE = 3;

For the iccOpenChannel command, the expected response format is:

< [channel ID length N (1 byte)] [channel ID (N bytes)] [response length M (1 byte)] [response (M bytes)]
  • channel ID length N is a 1-byte integer that contains the length of the channel ID field in bytes
  • channel ID is an N-byte integer (LSB first!) that specified the assigend channel ID
  • response length M is a 1-byte integer that contains the length of the response field
  • response is a byte array that contains response data received by the UICC in response to opening the channel to the given applet

Finally, this is what our iccOpenChannel method looks like:

@Override
public void iccOpenChannel(String AID, Message result) {
    if(AID == null) {
        AID = "";
    }

    final int aidLength = AID.length() / 2;
    byte[] aidBytes = new byte[aidLength];
    for (int i = 0; i < aidLength; ++i) {
        aidBytes[i] = (byte) Integer.parseInt(AID.substring(2 * i, 2 + 2 * i), 16);
    }

    ByteArrayOutputStream commandByteStream = new ByteArrayOutputStream();
    DataOutputStream commandOutputStream = new DataOutputStream(commandByteStream);
    try {
        commandOutputStream.writeByte(21);
        commandOutputStream.writeByte(9);
        commandOutputStream.writeShort(4 + aidLength);
        commandOutputStream.write(aidBytes);
    } catch (IOException e) {
        Log.e(LOG_TAG, "CMD_OPEN_CHANNEL : open fail", e);
        e.printStackTrace();
    }

    // inform response handler that this is a open channel request
    result.arg1 = EVENT_OPEN_CHANNEL_DONE;

    Log.d(LOG_TAG, "sendRawRequest - open channel: AID: "+ AID);
    invokeOemRilRequestRaw(commandByteStream.toByteArray(), result);
}

The iccCloseChannel method looks similiar. The only difference is that instead of the AID we pass the channel ID:

> [21] [10] [4 + 4 = 8 (channel ID is a 4-byte integer)] [channel ID (4 bytes, MSB first)]

Except for the special case of the channel ID zero, where we pass no channel ID at all:

> [21] [10] [4]
@Override
public void iccCloseChannel(int channel, Message result) {
    ByteArrayOutputStream commandByteStream = new ByteArrayOutputStream();
    DataOutputStream commandOutputStream = new DataOutputStream(commandByteStream);
    try {
        commandOutputStream.writeByte(21);
        commandOutputStream.writeByte(10);
        if (channel != 0) {
            commandOutputStream.writeShort(8);
            commandOutputStream.writeInt(channel);
        } else {
            commandOutputStream.writeShort(4);
        }
    } catch (IOException e) {
        Log.e(LOG_TAG, "CMD_CLOSE_CHANNEL : close fail", e);
    }

    // inform response handler that this is a close channel request
    result.arg1 = EVENT_CLOSE_CHANNEL_DONE;

    Log.d(LOG_TAG, "sendRawRequest - close channel "+ channel);
    invokeOemRilRequestRaw(commandByteStream.toByteArray(), result);
}

The iccExchangeAPDU command looks more complex but is basically just a set of data fields composed from the RIL_REQUEST_OEM_HOOK_RAW command structure and the transmitted APDU command. For a given APDU command (note that extended APDUs are not supported)

[cla (1 byte)] [ins (= command) (1 byte)] [p1 (1 byte)] [p2 (1 byte)] [[Lc (1 byte)] [data (Lc bytes)]] [Le (1 byte)]

the resulting iccExchangeAPDU command looks like:

> [21] [11] [4 + 9 + databytes.length] [cla] [command] [p1] [p2] [p3] [channel ID (4 bytes, MSB first)] [databytes]

Where p3 is either the command data length Lc and databytes is the concatenation of data and the expected response data length Le (if applicable) or p3 is the expected response data length Le and the databytes field is empty. For a Case-1 APDU, the a seperate command without those two fields is used:

> [21] [12] [4 + 8 = 12] [cla] [command] [p1] [p2] [channel ID (4 bytes, MSB first)]

The response to the iccExchangeAPDU command has the format of a response APDU according to ISO/IEC 7816-4:

< [response data (N bytes, optional)] [sw1] [sw2]
@Override
public void iccExchangeAPDU(int cla, int command, int channel,
                            int p1, int p2, int p3, String data,
                            Message result) {
    ByteArrayOutputStream commandByteStream = new ByteArrayOutputStream();
    DataOutputStream commandOutputStream = new DataOutputStream(commandByteStream);
    try {
        commandOutputStream.writeByte(21);

        int commandLength = 12;
        if (p3 == -1) {
            commandOutputStream.writeByte(12);
        } else {
            commandOutputStream.writeByte(11);
            ++commandLength;
        }

        byte[] dataBytes = null;
        if (data != null) {
            commandLength += data.length() / 2;

            dataBytes = new byte[data.length() / 2];
            for (int i = 0; i < dataBytes.length; ++i) {
                dataBytes[i] = (byte) Integer.parseInt(data.substring(2 * i, 2 + 2 * i), 16);
            }
        }

        commandOutputStream.writeShort(commandLength);
        commandOutputStream.writeByte(cla);
        commandOutputStream.writeByte(command);
        commandOutputStream.writeByte(p1);
        commandOutputStream.writeByte(p2);
        if (p3 != -1) {
            commandOutputStream.writeByte(p3);
        }
        commandOutputStream.writeInt(channel);
        if (dataBytes != null) {
            commandOutputStream.write(dataBytes);
        }
    } catch (IOException e) {
        Log.e(LOG_TAG, "CMD_ECHANGE_APDU: send failed - ", e);
    }

    Log.e(LOG_TAG, ": sendRawRequest - iccExchangeAPDU: "
                   + "Channel 0x" + Integer.toHexString(channel) + ": "
                   + " 0x" + Integer.toHexString(cla)
                   + " 0x" + Integer.toHexString(command)
                   + " 0x" + Integer.toHexString(p1)
                   + " 0x" + Integer.toHexString(p2)
                   + " 0x" + Integer.toHexString(p3)
    );

    // inform response handler that this is a exchange APDU request
    result.arg1 = EVENT_EXCHANGE_APDU_DONE;
    invokeOemRilRequestRaw(commandByteStream.toByteArray(), result);
}

Here is a complete diff containing the necessary changes to get SEEK with UICC access in the Android build for a Samsung Galaxy S3. Most important changes are in the frameworks/opt/telephony and the packages/apps/Phone repository.