65.9K
CodeProject 正在变化。 阅读更多。
Home

暴露智能卡核心功能的 WCF 服务

starIconstarIconstarIconstarIconstarIcon

5.00/5 (3投票s)

2013 年 1 月 23 日

CPOL

4分钟阅读

viewsIcon

29595

downloadIcon

738

本文介绍了一个 WCF 服务,用于公开与智能卡通信所需的 core 功能。

引言

在之前的文章中,我提出了一个 .NET 的智能卡框架,该框架已被大量开发者使用。我最近尝试了一些 WinRT 和 Windows Store 应用程序。其中之一是在 Windows Store 应用程序中使用智能卡。令我惊讶的是,我发现智能卡系统已被 Windows Store 应用程序或 WinRT 组件禁用。如果你尝试在 C++ WinRT 组件中包含 winscard.h,你将无法访问智能卡 API。

我不知道微软为什么这样做,但很多其他东西似乎也被 WinRT 组件或 Windows Store 应用程序禁用了。如果你需要从 Windows Store 应用程序访问智能卡,没有使用 API 的标准解决方案,但是仍然可以通过 WCF 服务从 WinRT 组件(或 Windows Store 应用程序)中使用它。

这就是我为了解决 Windows 8 的这个小问题而采用的解决方案。在本文中,我介绍了一个简单的 WCF 服务,它公开了与我的 .NET core 智能卡组件的接口 ICard 相同的方法。你可以在这篇文章中找到更多关于智能卡和正在使用的框架的信息。

智能卡服务

我制作了这个服务的第一个版本,非常简单。它只是公开了 ICard 接口,我几年前创建它用于封装连接到 PC 并使用符合 PC/SC 标准的智能卡读卡器的智能卡。

该接口定义如下

/// <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);
}

APDUCommandAPDUResponse 类在 WCF 服务中被重新定义为 DataContract,并且与我在原始智能卡框架项目中开发的原始 APDUCommandAPDUResponse 类不同。它们只是没有方法的 data 类,因此它们由 WCF 封送,并在服务的每一端由生成的代理实现。

当我实现一个不需要互操作的服务时,我通常创建可以在客户端和服务器中使用的接口,以简化代码并使其中一个客户端独立于代理。但在这种情况下,我打算在 WinRT 组件中使用该服务,而 WinRT 组件需要使用为 Windows Store 构建的 DLL。

本文仅介绍了 Windows 应用程序演示,WinRT 组件将在第二篇文章中介绍。

该服务是使用我之前开发的智能卡框架非常简单地实现的。

/// <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
}

我使用 Windows Forms 应用程序来托管该服务,因为在 Windows 7 以及 Windows 8 中,智能卡子系统被 Windows 服务禁用,并且无法访问智能卡。有一种方法可以授权智能卡子系统,但它必须由已登录的用户完成,所以我选择了 Windows Forms 应用程序,因为它可以在用户登录时自动启动并放入系统托盘,例如(在本示例中未完成)。

这个 WCF 服务可以被一个 WinRT 组件使用,该组件可以在 Windows store 应用程序中使用。这样,Windows store 应用程序就可以与智能卡通信了!

我不明白为什么微软禁用了 Windows store 中的智能卡支持,但这种方法允许绕过它。

智能卡是相对较慢的设备,因此在 localhost 上,WCF 的开销根本不明显。我为宿主提供了两种绑定,NetTcp 和 NamedPipe。如果定义了 NET_TCP,则绑定将是 NetTcp,否则将是 NamedPipe。如果需要,你可以增强应用程序并由程序进行选择。

演示应用程序

演示应用程序是一个非常简单的控制台应用程序,实际上是我在第一篇关于 .NET 的智能卡框架的文章中给出的那个。

它读取 SIM 卡的前 10 条 ADN 记录。我已经删除了 PIN 呈现,因此如果你运行它,你必须首先禁用卡的 PIN 或添加代码来验证 PIN。

这是代码

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);
    }
}

演示应用程序可以使用两种绑定运行。至于宿主,只需定义 NET_TCP 如果你想使用 NetTcp 绑定,否则它使用 NamedPipe 绑定。

结论

正如我提到的,这是解决方案的第一部分。在即将发布的文章中,我将提供一个 WinRT 组件,它连接到 WCF 服务并在一个简单的 Windows Store 应用程序中使用它。

敬请关注该解决方案的第二部分!

这篇文章附带的代码包含了前两篇文章的代码。这篇文章的项目是 SmartcardService、SCardServiceHost 和 DemoSCardService。

兴趣点

如果你需要将 Windows store 应用程序连接到智能卡,那么本文提供了解决方案。我还没有尝试使用 Crypto API 使用智能卡的证书,所以我不知道这是否仍然可行。

历史 

  •  2013 年 1 月 29 日,添加了对 WCF 故障的支持。更正了服务中 Transmit 实现中的一个潜在问题。

 

© . All rights reserved.