Driving a ACR122 NFC reader with the WINSCARD.DLL

Aug 4 2010 11:20 AM
Hi folks,

I recently acquired a touchatag reader which is in fact anr ACR122 NFC reader NFC disguised.
Unfortunately, the software supplied with the touchatag is not satisfacting, cause it only works on web applications. 

So I have looked at the ACR122 technical detail, I have noted that works on USB CCID and compliant PC/SC. So that mean we have the ability to manipulate it through the DLL WINSCARD.
 
Following this discovery, I have begun to develop a small test program to communicate between my PC and the reader. 

So far, I have managed to open a communication channel but all packages (ADPDU) sent to the reader come back with an error.

Example, when I want to get the firmware, I get the error 0x0A (See page 29 of the PDF below) and when I retrieve data, I get error 0x02 (A CRC error has-been detected).

My problem is that I don't understand why I get these errors.

Any ideas or suggestions are welcome. Thank you in advance for your help.

best regards,

Cédric.

Below, the PDF link « Application programming interface » of the reader ACR122:
http://www.acs.com.hk/drivers/eng/API_ACR122U.pdf

Ci-Below, C# code from the application test :

using System;
using System.Collections;
using System.Runtime.InteropServices;

namespace SimpleSmartcardDemo
{
        
    class Program
    {
        static void Main(string[] args)
        {
            if (SmartcardManager.EstablishContext())
            {
                //enumerating existing groups of readers
                ArrayList readerGroups = SmartcardManager.ListReaderGroups();
 
                if (readerGroups.Count != 0)
                {
                    Console.WriteLine("Groups of readers:");
                    foreach (object group in readerGroups)
                    {
                        Console.WriteLine("\t{0}", 
                            group.ToString());
                    }
                    Console.WriteLine();
                }
 
                //enumerating available readers
                ArrayList readers = SmartcardManager.ListReaders();
 
                if (readers.Count == 0)
                {
                    Console.WriteLine("No readers found.");
                }
                else
                {
                    Console.WriteLine("Readers found:");
                    foreach (object reader in readers)
                    {
                        Console.WriteLine("\t{0}", reader.ToString());

                        if (SmartcardManager.EstablishConnexion(reader.ToString()))
                        {
                            Console.WriteLine("\tEstablishConnexion {0}", reader.ToString());


                            if (SmartcardManager.BeginTransaction())
                            {
                                Console.WriteLine("\tBeginTransaction {0}", reader.ToString());

                                if (SmartcardManager.Transmit())
                                    Console.WriteLine("\tTransmit {0}", SmartcardManager._error);

                                if (SmartcardManager.EndTransaction())
                                    Console.WriteLine("\tEndTransaction {0}", reader.ToString());
                            }
                                
                            if (SmartcardManager.ReleaseConnexion())
                                Console.WriteLine("\tReleaseConnexion {0}", reader.ToString());

                        }
                    }
                }
 
                SmartcardManager.ReleaseContext();
            }
 
            Console.Write("\nPress any key...");
            Console.ReadKey();
        }
    }

    public enum ResourceManagerContext
    {
        User = 0,
        System = 2
    }

    public static class SmartcardManager
    {
        private static IntPtr _context;
        private static IntPtr pCardHandle;
        private static uint byActiveprotocol;

        public static string _error = string.Empty;

        public static bool EstablishContext()
        {
            ReleaseContext();
            uint result = SCardEstablishContext(ResourceManagerContext.System,IntPtr.Zero, IntPtr.Zero, ref _context);
            return (result == 0);
        }

        public static bool EstablishConnexion(string szReader)
        {
            // 3 param : dwShareMode (SCARD_SHARE_SHARED, SCARD_SHARE_EXCLUSIVE, SCARD_SHARE_DIRECT)
            // This application is allocating the reader for its private use, and will be controlling it directly. No other applications are allowed access to it.

            // 4 param : dwPreferredProtocols (SCARD_PROTOCOL_T0, SCARD_PROTOCOL_T1, 0)
            //  T=0 protocol: An asynchronous, character-oriented half-duplex transmission protocol.
            //  T=1 protocol: An asynchronous, block-oriented half-duplex transmission protocol.

            // 5 param :  pCardHandle : A handle that identifies the connection to the smart card in the designated reader.
            
            uint result = SCardConnect(_context, szReader, 2, (0x00000001), out pCardHandle, out byActiveprotocol);
            return (result == 0);

            // (0x00000001 | 0x00000002)
        }


        // The function waits for the completion of all other transactions before it begins. 
        // After the transaction starts, all other applications are blocked from accessing the smart card while the transaction is in progress.
        public static bool BeginTransaction()
        {
            uint result = SCardBeginTransaction(SmartcardManager.pCardHandle);
            return (result == 0);
        }

        public static bool EndTransaction()
        {
            // SCARD_LEAVE_CARD : No action occurs (0x0000)
            uint result = SCardEndTransaction(SmartcardManager.pCardHandle, (0x0000));
            return (result == 0);
        }

        public static bool ReleaseConnexion()
        {
            // SCARD_LEAVE_CARD : No action occurs (0x0000)
            uint result = SCardEndTransaction(SmartcardManager.pCardHandle, (0x0000));
            return (result == 0);

            // Error : The specified reader is not currently available for use
        }

        public static void ReleaseContext()
        {
            if (_context != IntPtr.Zero)
            {
                SCardReleaseContext(_context);
                _context = IntPtr.Zero;
            }
        }

        public static ArrayList ListReaders()
        {
            int bufsize = 0;

            //first call returns required buffer size
            SCardListReaders(_context, null, null, ref bufsize);
            String buffer = new String((char)0, bufsize);

            //retrieving list of connected readers
            uint result = SCardListReaders(_context, null, buffer, ref bufsize);
            string[] readers = buffer.Split(new char[] { (char)0 }, StringSplitOptions.RemoveEmptyEntries);
            return new ArrayList(readers);
        }

        public static ArrayList ListReaderGroups()
        {
            int bufsize = 0;

            //first call returns required buffer size
            SCardListReaderGroups(_context, null, ref bufsize);
            string buffer = new string((char)0, bufsize);

            //retrieving list of existing groups of readers
            uint result = SCardListReaderGroups(_context, buffer, ref bufsize);

            string[] groups = buffer.Split(new char[] { (char)0 }, StringSplitOptions.RemoveEmptyEntries);
            return new ArrayList(groups);
        }

        public static bool Transmit()
        {
            //  hCardHandle was set by a previous call to SCardConnect.

            // A pointer to the protocol header structure for the instruction :
            //The PCI info sent to the smart card,
            //I get the address of this PCI from "Winscard.dll",
            //and method "GetPciT0()" is defined bellow.
            IntPtr pioSendPci = GetPciT0();

            // A pointer to the actual data to be written to the card
            byte[] pbsendBuffer = SmartcardManager.GetSendBuffer();

            // The length, in bytes, of the pbSendBuffer parameter
            uint pbsendBufLen = (uint)pbsendBuffer.Length;

            // Pointer to the protocol header structure for the instruction, followed by a buffer in which to receive any returned protocol control information (PCI) specific to the protocol in use. This parameter can be NULL if no PCI is returned.
            SCARD_IO_REQUEST pioRecvPci = new SCARD_IO_REQUEST(0, 0);
            
            // Pointer to any data returned from the card
            byte[] pbRecvBuffer = new byte[255];

            // Supplies the length, in bytes, of the pbRecvBuffer parameter and receives the actual number of bytes received from the smart card. 
            uint pcbRecvLength = 255;

            uint result = SCardTransmit(pCardHandle, pioSendPci, pbsendBuffer, pbsendBufLen, pioRecvPci, pbRecvBuffer, pcbRecvLength);

            return (result == 0);
        }


        private static byte[] GetSendBuffer()
        {
            // APDU
            
            // Get the firmware (0x0A)
            /*
            string cla = "FF";  // the instruction class (The T=0 instruction class)
            string ins = "00";  // the instruction code (An instruction code in the T=0 instruction class)
            string p1 = "48";   // parameter to the instruction (Reference codes that complete the instruction code)
            string p2 = "00";   // parameter to the instruction (Reference codes that complete the instruction code)
            string lc = "00";   // size of I/O transfer (The number of data bytes to be transmitted during the command, per ISO 7816-4, Section 8.2.1)
            string body = "";
            */

            // Get data (0x02)
            
            string cla = "FF";  // the instruction class (The T=0 instruction class)
            string ins = "CA";  // the instruction code (An instruction code in the T=0 instruction class)
            string p1 = "00";   // parameter to the instruction (Reference codes that complete the instruction code)
            string p2 = "00";   // parameter to the instruction (Reference codes that complete the instruction code)
            string lc = "00";   // size of I/O transfer (The number of data bytes to be transmitted during the command, per ISO 7816-4, Section 8.2.1)
            string body = "";
            

            // Turn on green on RED and GREEN color LEDs (0x02)
            /*
            string cla = "FF";  // the instruction class (The T=0 instruction class)
            string ins = "00";  // the instruction code (An instruction code in the T=0 instruction class)
            string p1 = "40";   // parameter to the instruction (Reference codes that complete the instruction code)
            string p2 = "0F";   // parameter to the instruction (Reference codes that complete the instruction code)
            string lc = "04";   // size of I/O transfer (The number of data bytes to be transmitted during the command, per ISO 7816-4, Section 8.2.1)
            string body = "00000000";
            */

            // Get the current setting of the contactless interface (0x02)
            /*
            string cla = "FF";  // the instruction class (The T=0 instruction class)
            string ins = "00";  // the instruction code (An instruction code in the T=0 instruction class)
            string p1 = "00";   // parameter to the instruction (Reference codes that complete the instruction code)
            string p2 = "00";   // parameter to the instruction (Reference codes that complete the instruction code)
            string lc = "02";   // size of I/O transfer (The number of data bytes to be transmitted during the command, per ISO 7816-4, Section 8.2.1)
            string body = "D404";
            */

            string script = String.Format("{0}{1}{2}{3}{4}{5}", cla, ins, p1, p2,lc, body);
            byte[] buffer = new byte[script.Length / 2];
            for (int i = 0; i < script.Length; i = i + 2)
            {
                string temp = script.Substring(i, 2);
                buffer[i / 2] = byte.Parse(temp, System.Globalization.NumberStyles.HexNumber);
            }
            return buffer;
        }


        //
        // Private/Internal Types
        //

        [StructLayout(LayoutKind.Sequential)]
        public struct SCARD_IO_REQUEST
        {
            public SCARD_IO_REQUEST(int protocol, int length)
            {
                this.protocol = protocol;
                this.pciLength = length;
            }
            public int protocol;
            public int pciLength;
        }

        //Get the address of Pci from "Winscard.dll".
        static public IntPtr GetPciT0()
        {
            IntPtr handle = LoadLibrary("Winscard.dll");
            IntPtr pci = GetProcAddress(handle, "g_rgSCardT0Pci");
            FreeLibrary(handle);
            return pci;
        }

        // Prototypes :
        /*
        * 1 - SCardEstablishContext
        * 2 - SCardListReaders
        * 3 - SCardConnect
        * 4 - SCardBeginTransaction
        * 5 - SCardTransmit
        * 6 - SCardEndTransaction
        * 7 - SCardDisconnect
        */

        [DllImport("winscard.dll", CharSet = CharSet.Unicode)]
        static internal extern uint SCardEstablishContext(ResourceManagerContext scope, IntPtr reserved1, IntPtr reserved2, ref IntPtr context);

        [DllImport("winscard.dll", CharSet = CharSet.Unicode)]
        static internal extern uint SCardReleaseContext(IntPtr context);

        [DllImport("winscard.dll", CharSet = CharSet.Unicode)]
        static internal extern uint SCardListReaderGroups(IntPtr context, string groups, ref int size);

        [DllImport("winscard.dll", CharSet = CharSet.Unicode)]
        static internal extern uint SCardListReaders(IntPtr context, string groups, string readers, ref int size);

        [DllImport("winscard.dll", CharSet = CharSet.Unicode)]
        static extern uint SCardConnect(IntPtr hContext, string szReader, uint dwShareMode, uint dwPreferredProtocols, out IntPtr phCard, out uint pdwActiveProtocol);

        [DllImport("winscard.dll", CharSet = CharSet.Unicode)]
        static extern uint SCardBeginTransaction(IntPtr hCard);

        [DllImport("winscard.dll", CharSet = CharSet.Unicode)]
        static extern uint SCardEndTransaction(IntPtr hCard, uint dwDisposition);

        [DllImport("winscard.dll", CharSet = CharSet.Unicode)]
        static extern uint SCardDisconnect(IntPtr hCard, uint dwDisposition);

        [DllImport("winscard.dll", CharSet = CharSet.Unicode)]
        static extern uint SCardTransmit(IntPtr hCard, IntPtr sendPci, byte[] sendBuffer, uint sbLength, [In, Out] SCARD_IO_REQUEST recvPci, [Out] byte[] pbRecvBuffer, [In, Out] uint rbLength);

        //
        //
        //

        [DllImport("kernel32.dll")]
        private extern static IntPtr LoadLibrary(string fileName);

        [DllImport("kernel32.dll")]
        private extern static void FreeLibrary(IntPtr handle);

        [DllImport("kernel32.dll")]
        private extern static IntPtr GetProcAddress(IntPtr handle, string procName);

    }
}


Answers (4)