Developing Simple APDU Sender using WinSCard

In Windows operating system, we need WinSCard library to send and receive APDU. APDU is Application Protocol Data Unit, which is used to communicate to a smart card. There are two categories of APDUs: command APDUs and response APDUs. Using command APDUs, we can send an instruction to the smart card then it will yield response APDUs. In this article, we are going to develop a simple APDU sender that can be used to send the command APDUs to a smart card then expect the response from it. The complete source code can be found on this GitHub repository link https://github.com/WisnuAnggoro/simple-apdu-sender.

1. Dissecting mostly used functions in WinSCard.dll

Since we are going to call unmanaged code in WinSCard.dll from the managed code (C#), and the source code for the DLL is not available, we need to use Platform Invoke (P/Invoke) functionality for interoperating. We can find the complete interop code in WinSCard.cs file from this link. The following is explanation for each interop function.

1.1. SCardEstablishContext

The SCardEstablishContext function establishes the resource manager context (the scope) within which database operations are performed. In other word, the function will reserve the context to be used by the SCard resource.

[DllImport("winscard.dll")]
public static extern int SCardEstablishContext(
    uint dwScope,
    IntPtr notUsed1,
    IntPtr notUsed2,
    out IntPtr phContext);

To establish a context, we need to specify scope of the resource manager context. There are four possible value for this, and we are going to specify them in the SCardScopes enum.

public enum SCardScopes
{
    // Scope in user space.
    User,

    // Scope in terminal.
    Terminal,

    // Scope in system. Service on the local machine.
    System,

    // Scope is global.
    Global
}

We will have a handle to the established resource manager context if the SCardEstablishContext() function is successfully executed.

1.2. SCardReleaseContext

The SCardReleaseContext function closes an established resource manager context, freeing any resources allocated under that context. We have to invoke this function if we don’t need the context any longer (it’s usually called before we close the application).

[DllImport("winscard.dll")]
public static extern int SCardReleaseContext(
    IntPtr phContext);

To run this function, we have to supply the handle that identifies the resource manager context we’ve got from invocation of the SCardEstablishContext() function.

1.3. SCardListReaders

The SCardListReaders function provides the list of available smart card readers on the running device.

[DllImport("winscard.dll", 
    EntryPoint = "SCardListReadersA", 
    CharSet = CharSet.Ansi)]
public static extern int SCardListReaders(
    IntPtr hContext,
    byte[] mszGroups,
    byte[] mszReaders,
    ref UInt32 pcchReaders);

To run this function, we have to supply the handle that identifies the resource manager context we’ve got from invocation of the SCardEstablishContext() function. After successfully running the function, we will get the name of the available readers in the array of character in mszReaders and is pcchReaders bytes long.

1.4. SCardConnect

The SCardConnect function establishes a connection between the calling application and a smart card contained by a specific reader. The connection is established using a specific context we have established before using SCardEstablishContex. If no card exists in the specified reader, an error is returned.

[DllImport("winscard.dll")]
public static extern int SCardConnect(
    IntPtr hContext,
    string cReaderName,
    uint dwShareMode,
    uint dwPrefProtocol,
    ref IntPtr hCard,
    ref IntPtr ActiveProtocol);

To run this function, again we have to supply the handle we’ve got from invocation of the SCardEstablishContext() function. Also, we need a reader name we get from the invocation of SCardListReaders() function.

Other inputs we have to pass to this function is Share Mode and Prefered Protocol. The share mode itself is defined in SCardShareModes enum.

public enum SCardShareModes
{
    // This application will NOT allow others to share the reader. 
    // (SCARD_SHARE_EXCLUSIVE)
    Exclusive = 0x0001,

    // This application will allow others to share the reader. 
    // (SCARD_SHARE_SHARED)
    Shared = 0x0002,

    // Direct control of the reader, even without a card. 
    // (SCARD_SHARE_DIRECT)
    Direct = 0x0003
}

And the definition of the preferred protocol can be found in SCardProtocol enum.

public enum SCardProtocol
{
    // Protocol not defined.
    Unset = 0x0000,

    // T=0 active protocol
    T0 = 0x0001,

    // <summary>T=1 active protocol.
    T1 = 0x0002,

    // Raw active protocol. Use with memory type cards.
    Raw = 0x0004,

    // T=15 protocol.
    T15 = 0x0008,

    // IFD (Interface device) determines protocol.
    Any = (T0 | T1)
}

By running this function, we will have a handle that identifies the connection to the smart card in the designated reader stated in hCard and a flag that indicates the established active protocol stated in ActiveProtocol.

1.5. SCardDisconnect

The SCardDisconnect function terminates a connection previously opened between the calling application and a smart card in the target reader. By calling this function, other applications can connect to the same target reader.

[DllImport("winscard.dll")]
public static extern int SCardDisconnect(
    IntPtr hCard, 
    int Disposition);

To run this function, we have to pass the handle we’ve got from the SCardConnect() function. We also need to specify reader disposition that is defined in SCardReaderDisposition enum.

public enum SCardReaderDisposition
{

    // Do nothing. (SCARD_LEAVE_CARD)
    Leave = 0x0000,

    // Reset the card. (SCARD_RESET_CARD)
    Reset = 0x0001,

    // Unpower the card. (SCARD_UNPOWER_CARD)
    Unpower = 0x0002,

    // Eject the card. (SCARD_EJECT_CARD)
    Eject = 0x0003
}

1.6. SCardTransmit

The SCardTransmit function sends an APDU command to the smart card and expects to receive APDU response from the card.

[DllImport("winscard.dll")]
public static extern int SCardTransmit(
    IntPtr hCard, 
    ref SCARD_IO_REQUEST pioSendRequest,
    Byte[] SendBuff,
    int SendBuffLen,
    ref SCARD_IO_REQUEST pioRecvRequest,
    Byte[] RecvBuff, 
    ref int RecvBuffLen);

To run this function, we need to specify the IO request control as defined in SCARD_IO_REQUEST struct for both send and receive requests.

//IO Request Control
public struct SCARD_IO_REQUEST
{
    public int dwProtocol;
    public int cbPciLength;
}

Also, we have to pass the command APDU in SendBuff byte array format and specify its length in SendBuffLen. We will get the response APDU in RecvBuff in byte array format with RecvBuffLen length.

1.7. SCardGetStatusChange

The SCardGetStatusChange function blocks execution until the current availability of the cards in a specific set of readers changes.

[DllImport("winscard.dll", 
    CharSet = CharSet.Unicode)]
public static extern int SCardGetStatusChange(
    IntPtr hContext,
    int value_Timeout,
    ref SCARD_READERSTATE ReaderState,
    uint ReaderCount);

We need to run this function to know the current card’s ATR as stated in SCARD_READERSTATE struct.

//Reader State
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct SCARD_READERSTATE
{
    public string RdrName;
    public string UserData;
    public uint RdrCurrState;
    public uint RdrEventState;
    public uint ATRLength;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x24, ArraySubType = UnmanagedType.U1)]
    public byte[] ATRValue;
}

2. Wrapping all WinSCard functions to C# functions

We now have managed code for all needed functions in WinSCard.dll, thanks to P/Invoke functionality. We are going to wrap all these functions so we can implement it easily. The complete code can be found in SCardWrapper.cs file. Before we go further, we need to prepare several private variables like follows:

private IntPtr _context;
private IntPtr _cardHandle;
private uint _scope;
private uint _shareMode;
private uint _protocol;
IntPtr activeProtocol = IntPtr.Zero;

And we initialize the value of the preceding variable in the class constructor as follows:

public SCardWrapper(
    SCardScopes scope,
    SCardShareModes shareMode,
    SCardProtocol protocol)
{
    _scope = (uint)scope;
    _shareMode = (uint)shareMode;
    _protocol = (uint)protocol;
}

2.1. Establishing and Releasing a Context

To establish a context, we need to invoke SCardEstablishContext() function and pass NULL to the second and the third parameters since it’s reversed for future used. We will have a context if the function is successfully executed.

public bool EstablishContext()
{
    try
    {
        LastError = WinSCard.SCardEstablishContext(
            _scope,
            IntPtr.Zero,
            IntPtr.Zero,
            out _context);

        if (!IsSuccess(LastError))
            return false;

        return true;
    }
    catch (Exception)
    {
        throw;
    }
}

To release an established context, we need to invoke SCardReleaseContext() function and pass the context to it.

public bool ReleaseContext()
{
    try
    {
        LastError = WinSCard.SCardReleaseContext(
            _context);

        if (!IsSuccess(LastError))
            return false;

        return true;
    }
    catch (Exception)
    {
        throw;
    }
}

2.2. Listing the available readers

To get list of the available readers, we have to ensure we have established a context. Then, we can confidently invoke SCardListReaders() function. We need to call the function twice since we need to know the exact length of the readers name array. After we have the array, we can parse to find out the available readers. Don’t forget to release the context after running this process.

public List<string> GetReaderList()
{
    List<string> retval = new List<string>();
    uint pcchReaders = 0;
    int nullindex = -1;
    char nullchar = (char)0;

    try
    {
        // Establish context
        if (!EstablishContext())
            return null;

        // Find out length of the available readers character
        LastError = WinSCard.SCardListReaders(
            _context, 
            null, 
            null, 
            ref pcchReaders);

        if (!IsSuccess(LastError))
            return null;

        // Create a buffer and fill it
        byte[] szBuffer = new byte[pcchReaders];
        LastError = WinSCard.SCardListReaders(
            _context,
            null,
            szBuffer,
            ref pcchReaders);

        if (!IsSuccess(LastError))
            return null;

        // Convert szReaders to string
        string sBuffer = Encoding.ASCII.GetString(szBuffer);

        // Convert length of the available readers character to int
        int len = (int)pcchReaders;

        while (sBuffer[0] != (char)0)
        {
            nullindex = sBuffer.IndexOf(nullchar);
            string reader = sBuffer.Substring(0, nullindex);

            retval.Add(reader);

            len = len - (reader.Length + 1);
            sBuffer = sBuffer.Substring(nullindex + 1, len);
        }

        // Release context
        ReleaseContext();

        return retval;
    }
    catch (Exception)
    {
        throw;
    }
}

2.3. Connecting to and Disconnecting from a selected reader

To connect to reader, we have to specify the selected reader name. Fortunately, share mode and preferred protocol have been defined in the constructor.

public bool ConnectReader(string SelectedReader)
{
    try
    {
        // Connect to selected reader
        LastError = WinSCard.SCardConnect(
            _context,
            SelectedReader,
            _shareMode,
            _protocol,
            ref _cardHandle,
            ref activeProtocol);

        if (!IsSuccess(LastError))
            return false;

        return true;
    }
    catch (Exception)
    {
        throw;
    }
}

To disconnect from the selected reader, we need to specify the card handle and reader disposition, which is Unpower to power off the reader.

public bool DisconnectReader()
{
    try
    {
        // Disconnect to selected Card Handle
        LastError = WinSCard.SCardDisconnect(
            _cardHandle,
            (int)SCardReaderDisposition.Unpower);

        if (!IsSuccess(LastError))
            return false;

        return true;
    }
    catch (Exception)
    {
        throw;
    }
}

2.4. Sending a command APDU and expecting a response APDU

Before invoking SCardTransmit() command, we have to prepare invoke IO request handle for both sending and receiving process, which are sendRequest and receiveRequest. Then, we store the command APDU in the abAPDU byte array. After successfully running SCardTransmit() function, we will receive response APDU in abResp byte array.

public string SendAPDU(string sAPDU)
{
    SCARD_IO_REQUEST sendRequest;
    sendRequest.dwProtocol = (int)activeProtocol;
    sendRequest.cbPciLength = 8;

    SCARD_IO_REQUEST receiveRequest;
    receiveRequest.cbPciLength = 8;
    receiveRequest.dwProtocol = (int)activeProtocol;

    byte[] abAPDU = new byte[300];
    byte[] abResp = new byte[300];
    UInt32 wLenSend = 0;
    Int32 wLenRecv = 260;

    sAPDU = Utility.RemoveNonHexa(sAPDU);
    wLenSend = Utility.StrByteArrayToByteArray(sAPDU, ref abAPDU);

    LastError = WinSCard.SCardTransmit(
        _cardHandle, 
        ref sendRequest, 
        abAPDU, 
        (int)wLenSend, 
        ref receiveRequest, 
        abResp, 
        ref wLenRecv);

    if (!IsSuccess(LastError))
        return String.Empty;

    return Utility.ByteArrayToStrByteArray(
        abResp, 
        (UInt16)wLenRecv);
}

2.5. Resetting the card

We reset the card to make all states on the card to become default again. To reset it, we just need to disconnect card from the reader then connect it again to the reader. By invoking ResetReader() function, we also receive the ATR of the card in string format.

public bool ResetReader(
    string SelectedReader,
    out string ATR)
{
    String atr_temp = "";
    String s = "";
    bool boRet = true;

    ATR = atr_temp;

    // Disconnect to with reset disposition
    LastError = WinSCard.SCardDisconnect(
        _cardHandle,
        (int)SCardReaderDisposition.Reset);

    if (!IsSuccess(LastError))
        return false;

    // Connect to selected reader
    if (!ConnectReader(SelectedReader))
        return false;

    // Init readerState
    SCARD_READERSTATE readerState = new SCARD_READERSTATE
    {
        RdrName = SelectedReader,
        RdrCurrState = 0,
        RdrEventState = 0,
        UserData = "Card",
    };

    // Get Status Change
    LastError = WinSCard.SCardGetStatusChange(
        _context,
        0,
        ref readerState,
        1);

    if (!IsSuccess(LastError))
        return false;

    StringBuilder hex = new StringBuilder(readerState.ATRValue.Length * 2);
    foreach (byte b in readerState.ATRValue)
        hex.AppendFormat("{0:X2}", b);
    atr_temp = hex.ToString();

    ATR = atr_temp;
    return boRet;
}

References:

Should you have any queries, please do not hesitate to leave a comment below!