Nav links

Friday, 3 February 2017

DESFire communication in C#

Windows comes with built-in support for programmable access to smart cards using the unmanaged winscard.dll. Although you can use this directly in C# via the DllImport attribute I find this a bit fiddly, and prefer the lightweight PCSC wrapper provided by pcsc-sharp. This is available as Nuget package PCSC under this licence.

I currently only have access to, and interest in, DESFire cards, so what follows may only be relevant to them. I have written a small sample program, mainly based on the examples provided by the pcsc-sharp package, which demonstrates how to retrieve the application identifiers from the card, and its unique identifier (UID). Paste the code into a new C# Console Application, and if you have a USB card reader attached and a DESFire card, it should work straight away.

Note that all commands are sent and retrieved as hexadecimal bytes. You could define these in C# as bytes directly, such as
byte[] cmdGetApplicationIds = new byte[]{0xFF, 0xCA, 0x00, 0x00, 0x00};

but I found it more readable to define them as strings and convert when required. See functions StringToByte() and ShowBytes() for how this is done.



using PCSC;
using System;
using System.Linq;

namespace DESFire.Communications
{
    class Program
    {
        static void Main(string[] args)
        {
            var cmdGetApplicationIds = StringToByte("6A");
            var cmdGetUID = StringToByte("FF CA 00 00 00");

            try
            {
                SCardContext hContext;
                SCardReader reader;
                SCardError err;
                IntPtr pioSendPci;
                SetUpReader(out reader, out err, out pioSendPci, out hContext);

                var response = SendCommand(reader, ref err, pioSendPci, 
                    cmdGetApplicationIds, "Get application IDs");
                response = SendCommand(reader, ref err, pioSendPci, 
                    cmdGetUID, "Get UID");

                hContext.Release();
            }
            catch (PCSCException ex)
            {
                Console.WriteLine("Ouch: "
                    + ex.Message
                    + " (" + ex.SCardError.ToString() + ")");
            }

            // Keep console window open if running from Visual Studio
            Console.ReadKey();
        }

        private static void SetUpReader(out SCardReader reader, out SCardError err, 
            out IntPtr pioSendPci, out SCardContext hContext)
        {
            // Establish SCard context
            hContext = new SCardContext();
            hContext.Establish(SCardScope.System);

            // Retrieve the list of Smartcard readers
            string[] szReaders = hContext.GetReaders();
            if (szReaders.Length <= 0)
                throw new PCSCException(SCardError.NoReadersAvailable,
                    "Could not find any Smartcard reader.");

            Console.WriteLine("reader name: " + szReaders[0]);

            // Create a reader object using the existing context
            reader = new SCardReader(hContext);

            // Connect to the card
            err = reader.Connect(szReaders[0],
                SCardShareMode.Shared,
                SCardProtocol.T0 | SCardProtocol.T1);
            CheckErr(err);

            switch (reader.ActiveProtocol)
            {
                case SCardProtocol.T0:
                    pioSendPci = SCardPCI.T0;
                    break;
                case SCardProtocol.T1:
                    pioSendPci = SCardPCI.T1;
                    break;
                default:
                    throw new PCSCException(SCardError.ProtocolMismatch,
                        "Protocol not supported: "
                        + reader.ActiveProtocol.ToString());
            }
        }

        /// <summary>
        /// Send command to smart card, displaying request and response to console
        /// </summary>
        /// <param name="message">optional message to display</param>
        /// <returns></returns>
        private static byte[] SendCommand(SCardReader reader, ref SCardError err, 
            IntPtr pioSendPci, byte[] command, string message = "")
        {
            byte[] buffer = new byte[256];

            Console.WriteLine();
            Console.WriteLine(message);
            ShowBytes(command, " request");
            err = reader.Transmit(pioSendPci, command, ref buffer);
            CheckErr(err);
            ShowBytes(buffer, "response");
            return buffer;
        }

        static void CheckErr(SCardError err)
        {
            if (err != SCardError.Success)
                throw new PCSCException(err,
                    SCardHelper.StringifyError(err));
        }

        /// <summary>
        /// Convert string of hexadecimal text into array of bytes
        /// </summary>
        /// <param name="hexWithSpaces">string of hex bytes with optional spaces</param>
        /// <returns>array of bytes</returns>
        public static byte[] StringToByte(string hexWithSpaces)
        {
            var hex = hexWithSpaces.Replace(" ", "");
            return Enumerable.Range(0, hex.Length)
                             .Where(x => x % 2 == 0)
                             .Select(x => Convert.ToByte(hex.Substring(x, 2), 16))
                             .ToArray();
        }

        /// <summary>
        /// Display array of bytes on console
        /// </summary>
        /// <param name="resultArray">bytes to show</param>
        /// <param name="message">optional message to display</param>
        private static void ShowBytes(byte[] resultArray, string message = "")
        {
            Console.Write((message + ": ").PadLeft(20, ' '));
            for (int i = 0; i < resultArray.Length; i++)
                Console.Write("{0:X2} ", resultArray[i]);
            Console.WriteLine();
        }
    }
}
Now that you can communicate with your DESFire card you probably want to authenticate.