Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

A WCF service to expose the core functions of a smart card

5.00/5 (3 votes)
1 Feb 2013CPOL4 min read 29.1K   708  
This article introduces a WCF service to expose the core functions necessary to communicate with a smart card.

Introduction

In a previous article I proposed a Smart Card framework for .NET that has been used so far by a large number of developers. I recently tried a few things with WinRT and Windows Store applications. One of them was to use a smart card in a Windows Store application. To my surprise I discovered that the smart card system has been disabled for a Windows Store application or a WinRT component. If you try to include winscard.h in a C++ WinRT component, you won't get access to the smart card API.

I don't know why Microsoft did that but well, there are many other stuff that seem to have been disabled for WinRT components or Windows Store applications. In the case where you need to access to a smart card from a Windows Store application there is no standard solution using an API, however it is still possible to use a WCF service from a WinRT component (or a Windows Store app).

This is the solution I adopted to work around this little problem of Windows 8. In this article I introduce a simple WCF service that exposes the same methods as the interface ICard of my core smart card component for .NET. You can find more information about smart cards and the framework I'm using in this article.

The smart card service

I made the first version of this service very simple. It simply exposes the ICard interface that I created a few years ago to encapsulate a smart card connected to the PC using a PC/SC compliant smart card reader.

The interface is defined as follows:

/// <summary>
/// Contract to manage a smart card connected to a smart card reader
/// </summary>
[ServiceContract]
public interface IRemoteCard
{
    /// <summary>
    /// Gets the list of readers
    /// </summary>
    /// <returns>A string array of the readers</returns>
    [OperationContract]
    [FaultContract(typeof(SmartcardFault))]
    [FaultContract(typeof(GeneralFault))]
    string[] ListReaders();

    /// <summary>
    /// Connects to a card. Establishes a card session
    /// </summary>
    /// <param name="Reader">Reader string</param>
    /// <param name="ShareMode">Session share mode</param>
    /// <param name="PreferredProtocols">Session preferred protocol</param>
    [OperationContract]
    [FaultContract(typeof(SmartcardFault))]
    [FaultContract(typeof(GeneralFault))]
    void Connect(string Reader, SHARE ShareMode, PROTOCOL PreferredProtocols);

    /// <summary>
    /// Disconnect the current session
    /// </summary>
    /// <param name="Disposition">Action when disconnecting from the card</param>
    [FaultContract(typeof(SmartcardFault))]
    [FaultContract(typeof(GeneralFault))]
    [OperationContract]
    void Disconnect(DISCONNECT Disposition);

    /// <summary>
    /// Transmit an APDU command to the card
    /// </summary>
    /// <param name="ApduCmd">APDU Command to send to the card</param>
    /// <returns>An APDU Response from the card</returns>
    [OperationContract]
    [FaultContract(typeof(SmartcardFault))]
    [FaultContract(typeof(GeneralFault))]
    APDUResponse Transmit(APDUCommand ApduCmd);

    /// <summary>
    /// Begins a card transaction
    /// </summary>
    [OperationContract]
    [FaultContract(typeof(SmartcardFault))]
    [FaultContract(typeof(GeneralFault))]
    void BeginTransaction();

    /// <summary>
    /// Ends a card transaction
    /// </summary>
    [OperationContract]
    [FaultContract(typeof(SmartcardFault))]
    [FaultContract(typeof(GeneralFault))]
    void EndTransaction(DISCONNECT Disposition);

    /// <summary>    /// Gets the attributes of the card
    /// 
    /// This command can be used to get the Answer to reset
    /// </summary>
    /// <param name="AttribId">Identifier for the Attribute to get</param>
    /// <returns>Attribute content</returns>
    [OperationContract]
    [FaultContract(typeof(SmartcardFault))]
    [FaultContract(typeof(GeneralFault))]
    byte[] GetAttribute(UInt32 AttribId);
}

The APDUCommand and the APDUResponse classes are redefined for the WCF service as a DataContract and are different from the original APDUCommand and APDUResponse classes I developed in the original smart card framework project. They are just data classes without methods so they are marshaled by WCF and  implemented in each side of the service by generated proxies.

When I implement a service that doesn't have to be inter-operable I usually create interfaces that I can use in both the client and the server to simplify the code and make one of the clients independent of the proxy. But in this case I intend to use the service in a WinRT component and a WinRT component needs to use DLLs that are built for the Windows Store.

This first article only introduces a Windows application demonstration, the WinRT component will be presented in a second article.

The service is implemented very simply using the smart card framework I previously developed.

C#
/// <summary>
/// Implements the IRemoteCard interface as WCF service
/// 
/// This class uses the CardNative object that implements the ICard interface
/// </summary>
public class RemoteCard : IRemoteCard
{
    private CardNative card = new CardNative();

    #region ICard interface

    public string[] ListReaders()
    {
        try
        {
            return card.ListReaders();
        }
        catch (SmartCardException scEx)
        {
            SmartcardFault scFault = new SmartcardFault(scEx);
            throw new FaultException<SmartcardFault>(scFault);
        }
        catch (Exception ex)
        {
            GeneralFault genFault = new GeneralFault(ex);
            throw new FaultException<GeneralFault>(genFault);
        }
    }

    public void Connect(string reader, SHARE shareMode, PROTOCOL preferredProtocols)
    {
        try
        {
            card.Connect(reader, shareMode, preferredProtocols);
        }
        catch (SmartCardException scEx)
        {
            SmartcardFault scFault = new SmartcardFault(scEx);
            throw new FaultException<SmartcardFault>(scFault);
        }
        catch (Exception ex)
        {
            GeneralFault genFault = new GeneralFault(ex);
            throw new FaultException<GeneralFault>(genFault);
        }
    }

    public void Disconnect(DISCONNECT disposition)
    {
        try
        {
            card.Disconnect(disposition);
        }
        catch (SmartCardException scEx)
        {
            SmartcardFault scFault = new SmartcardFault(scEx);
            throw new FaultException<SmartcardFault>(scFault);
        }
        catch (Exception ex)
        {
            GeneralFault genFault = new GeneralFault(ex);
            throw new FaultException<GeneralFault>(genFault);
        }
    }

    public APDUResponse Transmit(APDUCommand apduCmd)
    {
        try
        {
            GemCard.APDUCommand apduCommand = new GemCard.APDUCommand(
                apduCmd.Class,
                apduCmd.Ins,
                apduCmd.P1,
                apduCmd.P2,
                (apduCmd.Data == null || apduCmd.Data.Length == 0) ? null : apduCmd.Data,
                apduCmd.Le);

            GemCard.APDUResponse apduResponse = card.Transmit(apduCommand);

            return new APDUResponse()
            {
                Data = apduResponse.Data,
                SW1 = apduResponse.SW1,
                SW2 = apduResponse.SW2
            };
        }
        catch (SmartCardException scEx)
        {
            SmartcardFault scFault = new SmartcardFault(scEx);
            throw new FaultException<SmartcardFault>(scFault);
        }
        catch (Exception ex)
        {
            GeneralFault genFault = new GeneralFault(ex);
            throw new FaultException<GeneralFault>(genFault);
        }
    }

    public void BeginTransaction()
    {
        try
        {
            card.BeginTransaction();
        }
        catch (SmartCardException scEx)
        {
            SmartcardFault scFault = new SmartcardFault(scEx);
            throw new FaultException<SmartcardFault>(scFault);
        }
        catch (Exception ex)
        {
            GeneralFault genFault = new GeneralFault(ex);
            throw new FaultException<GeneralFault>(genFault);
        }
    }

    public void EndTransaction(DISCONNECT disposition)
    {
        try
        {
            card.EndTransaction(disposition);
        }
        catch (SmartCardException scEx)
        {
            SmartcardFault scFault = new SmartcardFault(scEx);
            throw new FaultException<SmartcardFault>(scFault);
        }
        catch (Exception ex)
        {
            GeneralFault genFault = new GeneralFault(ex);
            throw new FaultException<GeneralFault>(genFault);
        }
    }

    public byte[] GetAttribute(uint attribId)
    {
        try
        {
            return card.GetAttribute(attribId); ;
        }
        catch (SmartCardException scEx)
        {
            SmartcardFault scFault = new SmartcardFault(scEx);
            throw new FaultException<SmartcardFault>(scFault);
        }
        catch (Exception ex)
        {
            GeneralFault genFault = new GeneralFault(ex);
            throw new FaultException<GeneralFault>(genFault);
        }
    }

    #endregion
}

I used a Windows Forms application to host the service because in Windows 7 and so Windows 8, the smart card sub-system is disabled for a Windows service and a smart card cannot be accessed. There is a way to authorize the smart card sub-system but it must be done from a logged user, so I chose the Windows Forms application as it can be started automatically when the user logs in and put in the system tray, for example (not done in this sample).

This WCF service can be consumed by a WinRT component that can be used in a Windows store application. That way it is possible for the Windows store application to communicate with the smart card!

I don't really understand why Microsoft disabled the smart card support in Windows store, but this method allows to work it around.

Smart cards are relatively slow devices so on the localhost the overhead of WCF is simply not noticeable. I provide two bindings for the host, NetTcp and NamedPipe. If you define NET_TCP the binding will be NetTcp, otherwise it will be NamedPipe. You can enhanced the application and make the selection by the program if desired.

The demo application

The demo application is a very simple console application which is in fact the one I gave in my first smart card framework article for .NET.

It reads the 10 first ADN records of a SIM card. I have removed the PIN presentation, so if you run it you must first disable the PIN of the card or add the code to verify the PIN.

Here is the code:

C#
static void DemoWithNAMEDPIPEService()
{
    try
    {
        SCardNPService.IRemoteCard remoteCard = new SCardNPService.RemoteCardClient();

        string[] readers = remoteCard.ListReaders();
        Console.WriteLine("Readers:");

        foreach (string reader in readers)
        {
            Console.WriteLine("    " + reader);
        }

        if (readers.Length > 0)
        {
            remoteCard.Connect(readers[0], SCardNPService.SHARE.Shared, SCardNPService.PROTOCOL.T0orT1);
            Console.WriteLine("Session opened with the remote card on reader " + readers[0]);

            SCardNPService.APDUCommand
                apduSelectFile = new SCardNPService.APDUCommand()
                {
                    Class = 0xA0,
                    Ins = 0xA4,
                    P1 = 0,
                    P2 = 0,
                    Data = null,
                    Le = 0
                },
                apduReadRecord = new SCardNPService.APDUCommand()
                {
                    Class = 0xA0,
                    Ins = 0xB2,
                    P1 = 1,
                    P2 = 4,
                    Data = null,
                    Le = 0
                },
                apduGetResponse = new SCardNPService.APDUCommand()
                {
                    Class = 0xA0,
                    Ins = 0xC0,
                    P1 = 0,
                    P2 = 0,
                    Data = null,
                    Le = 0
                };

            // Select MF
            apduSelectFile.Data = new byte[] { 0x3F, 0x00 };
            SCardNPService.APDUResponse response = remoteCard.Transmit(apduSelectFile);
            if (response.SW1 == SC_PENDING)
            {
                // Select EFtelecom
                apduSelectFile.Data = new byte[] { 0x7F, 0x10 };
                response = remoteCard.Transmit(apduSelectFile);
                if (response.SW1 == SC_PENDING)
                {
                    // Select EFadn
                    apduSelectFile.Data = new byte[] { 0x6F, 0x3A };
                    response = remoteCard.Transmit(apduSelectFile);
                    if (response.SW1 == SC_PENDING)
                    {
                        apduGetResponse.Le = response.SW2;
                        response = remoteCard.Transmit(apduGetResponse);
                        if (response.SW1 == SC_OK_HI)
                        {
                            // Get the length of the record
                            int recordLength = response.Data[14];

                            Console.WriteLine("Reading the Phone number 10 first entries");
                            // Read the 10 first record of the file
                            for (int nI = 0; nI < 10; nI++)
                            {
                                apduReadRecord.Le = (byte)recordLength;
                                apduReadRecord.P1 = (byte)(nI + 1);
                                response = remoteCard.Transmit(apduReadRecord);

                                if (response.SW1 == SC_OK_HI)
                                {
                                    Console.WriteLine("Record #" + (nI + 1).ToString());
                                    Console.WriteLine(BufferToString(response.Data));
                                }
                            }
                        }
                    }
                }
            }

            Console.WriteLine("Press a key to close the session...");

            Console.ReadKey();

            remoteCard.Disconnect(SCardNPService.DISCONNECT.Unpower);
            Console.WriteLine("Session closed with the remote card");
        }
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
    }
}

The demo application can run with both bindings. As for the host, just define NET_TCP if you want to use the NetTcp binding, otherwise it uses the NamedPipe binding.

Conclusion

As I mentioned, this is the first part of the solution. In a soon to be released article, I will provide a WinRT component that connects to the WCF service and use it in a simple Windows Store application.

So stay tuned for the second part of this solution!

The code attached to this article contains the code of the two previous articles. The projects of that article are SmartcardService, SCardServiceHost, and DemoSCardService.

Points of interest 

If you need to connect a Windows store application to a smart card, then this article provides the solution. I haven't tried to use a certificate of a smart card store using the Crypto API, so I don't know if this is still possible. 

History 

  •  29 Janv 2013, Adding support for WCF fault. Corrects a potential issue in the implementation of Transmit in the service. 

 

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)