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

适用于 .NET 的智能卡框架

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.88/5 (98投票s)

2006年12月5日

CPOL

8分钟阅读

viewsIcon

2243243

downloadIcon

146120

介绍一个使用 .NET 连接 PCSC 智能卡 API 的框架。

介绍  

 2013 年 2 月 18 日,本文的浏览量突破了 500,000 次。感谢所有访客!虽然在互联网上这只是一个小数字,但我很惊讶,这个几乎 10 年前用 .Net 1.0 Beta 版本编写的小库仍然如此受欢迎。另一件让我感到困惑的事情是,10 年后 .NET Framework 中仍然没有对智能卡的原生支持(除了加密服务提供商)。无论如何,我将进行一些重构以改进它,因为当前的架构有点陈旧,而且不是那么面向对象! 

.NET Framework 于 2002 年推出,3.0 版本于 11 月发布。到目前为止,微软尚未在 .NET 中包含智能卡类,如果您想开发支持智能卡的应用程序,则必须自己开发类。幸运的是,在 .NET 中重用现有代码比 Java 容易得多。在 Windows 中,如果您需要使用智能卡,只需在程序中使用 PC/SC API。此 API 提供 C 函数或包装 PC/SC 函数的 COM 对象。 .NET Framework 提供了两种与旧代码进行互操作的方式:COM 互操作和用于原生代码互操作的 P/Invoke 功能。我强烈建议使用原生代码,因为它允许更多的交互,特别是更好的事件支持。 

背景  

本文演示了如何使用 .NET 的互操作功能,并利用它们来编写一个简单的框架,在您的应用程序中使用智能卡。智能卡是一种小型嵌入式设备,通过读卡器使用 PC/SC Win32 API 接收命令。如果您想使用此 API,则需要一个智能卡读卡器来使用 SIM 卡等智能卡。

一个简单的智能卡框架

我将要介绍的 SC 框架由一个用于与智能卡通信的接口、几个用于封装智能卡命令不同参数的类,以及取决于我们使用的互操作模式的实现类组成。

智能卡接口为 .NET 程序提供了对智能卡的简单访问。稍后我们将看到如何使用两种互操作技术来实现此接口。

spublic interface ICard
{
    string[] ListReaders();
    void Connect(string Reader, SHARE ShareMode, 
                 PROTOCOL PreferredProtocols);
    void Disconnect(DISCONNECT Disposition);
    APDUResponse Transmit(APDUCommand ApduCmd);
    void BeginTransaction();
    void EndTransaction(DISCONNECT Disposition);
}

APDUCommandAPDUResponse 类用于发送命令并从卡获取响应。SHAREPROTOCOLDISCONNECT 是 PC/SC 使用的常量。

public class APDUCommand
{
    public APDUCommand(byte bCla, byte bIns, 
           byte bP1, byte bP2, byte[] baData, byte bLe);
    public void Update(APDUParam apduParam);
    public override string ToString();
    public byte    Class;
    public byte    Ins;
    public byte    P1;
    public byte    P2;
    public byte[]  Data;
    public byte    Le;
}

public class APDUResponse
{
    public APDUResponse(byte[] baData);
    public byte[]    Data;
    public byte    SW1;
    public byte    SW2;
    public ushort    Status;
    public override string ToString();
}

添加卡事件支持

当卡插入读卡器或从中移除时,PC/SC 允许您处理这些事件。因此,我为这个框架添加了基于 .NET 事件模型的卡事件支持。CardBase 类继承自 ICard 接口,实现了对 CardInsertedCardRemoved 这两个事件的支持。检测机制在实现 ICard 的派生类中实现。到目前为止,我只在 CardNative 中实现了此支持。如果您想在应用程序中支持该事件,只需实现 CardInsertedEventHandlerCardRemovedEventHandler

abstract public class CardBase : ICard
{
    public event CardInsertedEventHandler OnCardInserted = null;
    public event CardRemovedEventHandler OnCardRemoved = null;
          
    abstract public string[] ListReaders();
    abstract public void Connect(string Reader, 
             SHARE ShareMode, PROTOCOL PreferredProtocols);
    abstract public void Disconnect(DISCONNECT Disposition);
    abstract public APDUResponse Transmit(APDUCommand ApduCmd);
    abstract public void BeginTransaction();
    abstract public void EndTransaction(DISCONNECT Disposition);
          
    public void StartCardEvents(string Reader);
    public void StopCardEvents();
          
    abstract protected void RunCardDetection();
          
    protected void CardInserted();
    protected void CardRemoved();
}

我开发了 ICard 接口的两个实现。一个是使用 COM 互操作,另一个是使用 P/Invoke 进行原生互操作。这两种实现方式的行为方式相同。此外,我还为 Compact Framework 移植了 P/Invoke 实现,因此可以为 Pocket PC 开发智能卡应用程序。

CardCOM:COM 互操作实现类

COM 互操作是在 .NET Framework 中重用旧代码的最简单方法。所有版本的 Visual Studio .NET 都为 COM 提供了非常好的支持。您只需添加一个对需要导入的 COM 对象的引用,它就会生成一个包装器 DLL 和所有必要的类来使用您的 COM 接口。基本上,您无需编写任何代码。但是,您的 COM 组件必须遵守一些规则,特别是在方法使用的参数方面。PC/SC COM 组件的不同接口是在 .NET 存在很久之前编写的,其中一些接口与 COM 互操作不完全兼容。这就是为什么我必须开发我自己的 COM 接口来获取读者列表。对于最重要的接口 ISCard 及其使用的接口,导入工作顺利。

interface ISCardDatabaseEx : IDispatch{
    [id(1), helpstring("method ListReaders")] 
       HRESULT ListReaders([out,retval] VARIANT* ppReaders);
};

我开发的 COM 组件替换了 ISCardDatabase 接口,并且只实现了一个方法:ListReaders。在 Microsoft 的实现中,返回参数是 BSTRSAFEARRAY,不幸的是 .NET 无法正确导入。正确的方法是使用一个包含 BSTRSAFEARRAYVARIANT*。然后,.NET 会生成一个包装方法,该方法返回一个您只需强制转换为 string[] 的对象。在 .NET 中使用 COM 对象就像在代码中添加对该对象的引用一样简单。Visual Studio 将生成一个您可以在代码中直接使用的包装类。以下摘录说明了这一点。

using SCARDSSPLib;    // use the SCard COM object

/// <summary>
/// Default constructor
/// </summary>
public CardCOM()
{
    // Create the SCard object
    m_itfCard = new CSCardClass();
}

public override void Connect(string Reader, 
       SHARE ShareMode, PROTOCOL PreferredProtocols)
{
    // Calls AttachReader to connect to the card
    m_itfCard.AttachByReader(Reader, 
        (SCARD_SHARE_MODES) ShareMode, 
        (SCARD_PROTOCOLS) PreferredProtocols);
}

ISCardDatabase 接口在必须使用 Regsvr32 注册的 DLL 中提供。

CardNative:使用 P/Invoke 的原生互操作实现类

平台调用机制 (P/Invoke) 是一个非常强大的机制,它提供了对 Win32 平台 API 的完全访问。 .NET Framework 提供了一套完整的类,您可以使用它们来实现从 .NET 调用 Win32 函数所需的任何封送操作。这些类定义在 System.Runtime.InteropServices 程序集中,您只需将其导入到您的程序中。P/Invoke 机制虽然有点复杂,但比 Java 的 JNI 机制方便得多。所有原子类型,如 intbytelong 等... 都会被编译器自动封送。byte[] 也会被自动封送为输入或输出参数。当您需要处理更复杂的参数,如字符串、结构或结构指针时,.NET 提供了一组封送类,在封送参数时可以在程序中用作属性或对象。为了开发 CardNative 类,我无需开发任何额外的代码来使用 PC/SC API,所有代码都在源文件 CardNative.cs 中。当您想使用 Win32 API C 函数时,您需要使用 System.Runtime.InteropServices 程序集提供的互操作属性在类中声明该函数。一旦声明了函数,就可以像使用任何 C# 方法一样在类中使用它。以下代码示例说明了这种机制。

[DllImport("winscard.dll", SetLastError=true)]
internal static extern    int    SCardTransmit(UInt32 hCard,
    [In] ref SCard_IO_Request pioSendPci,
    byte[] pbSendBuffer,
    UInt32 cbSendLength,
    IntPtr pioRecvPci,
    [Out] byte[] pbRecvBuffer,
    out UInt32 pcbRecvLength
);

public override APDUResponse Transmit(APDUCommand ApduCmd)
{
    uint    RecvLength = (uint) (ApduCmd.Le + APDUResponse.SW_LENGTH);
    byte[]    ApduBuffer = null;
    byte[]    ApduResponse = new byte[ApduCmd.Le + APDUResponse.SW_LENGTH];
    SCard_IO_Request    ioRequest = new SCard_IO_Request();
    ioRequest.m_dwProtocol = m_nProtocol;
    ioRequest.m_cbPciLength = 8;

    // Build the command APDU

    if (ApduCmd.Data == null)
    {
        ApduBuffer = new byte[APDUCommand.APDU_MIN_LENGTH + 
                    ((ApduCmd.Le != 0) ? 1 : 0)];

        if (ApduCmd.Le != 0)
            ApduBuffer[4] = (byte) ApduCmd.Le;
    }
    else
    {
        ApduBuffer = new byte[APDUCommand.APDU_MIN_LENGTH + 1 + 
                              ApduCmd.Data.Length];
        for (int nI = 0; nI < ApduCmd.Data.Length; nI++)
            ApduBuffer[APDUCommand.APDU_MIN_LENGTH + 1 + nI] = 
                                              ApduCmd.Data[nI];
        ApduBuffer[APDUCommand.APDU_MIN_LENGTH] = 
                 (byte) ApduCmd.Data.Length;
    }

    ApduBuffer[0] = ApduCmd.Class;
    ApduBuffer[1] = ApduCmd.Ins;
    ApduBuffer[2] = ApduCmd.P1;
    ApduBuffer[3] = ApduCmd.P2;

    m_nLastError = SCardTransmit(m_hCard, 
        ref ioRequest, 
        ApduBuffer, 
        (uint) ApduBuffer.Length, 
        IntPtr.Zero, ApduResponse, out RecvLength); 

    if (m_nLastError != 0)
    {
        string msg = "SCardTransmit error: " + m_nLastError;
        throw new Exception(msg);
    }

    byte[] ApduData = new byte[RecvLength];

    for (int nI = 0; nI < RecvLength; nI++)
        ApduData[nI] = ApduResponse[nI];
 
    return new APDUResponse(ApduData);
}

演示应用程序

智能卡 API 非常易于使用。但是,如果您想编写一个智能卡应用程序,您必须知道要发送到卡上的命令,以执行诸如选择文件、验证 PIN 或读取数据之类的操作。这些命令称为 APDU 命令,并在您要访问的智能卡的规范中有描述。本文的目的不是解释如何使用智能卡,而是为您提供一个简单的 C# API,以便您可以玩转任何您想要的智能卡。

大多数人都有 GSM 手机,所以如果您在 PC 上有智能卡读卡器(有些笔记本电脑配有内置智能卡读卡器),您可以使用这个小演示程序来玩您的 SIM 卡。这个简单的程序会显示 PIN(如果您的 PIN 已激活),选择电话号码文件,并读取该文件的前 10 条记录。您将获得的所有数据都是以二进制格式存储在卡中的。如果您的 PIN 已激活,您必须在我的代码中取消注释验证 PIN 的行。PIN 值以二进制形式输入。如果您的 PIN 是 1234,您必须输入 31323334 并用 FF 字节填充,直到 PIN 长度为 8。

在本文的第二部分,我将介绍一个更高级的智能卡应用程序开发框架。这个框架将使用我在本文中描述的类,并使编写智能卡应用程序更加容易。

WCS 智能卡服务

在最近的一篇 文章 中,我添加了一个 WCF 服务来公开 NativeCard 类实现的 ICard 接口。通过这样做,我对原始代码中的异常支持进行了一些改进。

目前仅支持 32 位,并且卡事件尚未映射到 WCF 服务。 

兴趣点 

在第一部分中,我们看到了两种使用 .NET 处理旧代码的方法。这是 .NET 非常强大的功能之一,使其成为开发 Windows 平台应用程序的理想托管框架。如果您想在 .NET 代码中访问智能卡应用程序,这个智能卡框架可能会非常有用。当然,VB.NET 用户也可以使用这个框架。

然而,这组类仍然要求您编写专用代码来使用 APDU 命令访问卡,而这并不是最有趣的代码。在本文的第二部分,我将提供一个 XML 框架,允许您使用 XML 声明以最少的代码编写智能卡应用程序。

历史

  • 2007 年 3 月 5 日 - 添加了对 ATR 和其他属性的支持,PC/SC 的 SCardGetAttrib 函数
  • 2011 年 8 月 16 日 - 添加了适用于 Visual Studio 2010 的项目更新
  • 2011 年 8 月 26 日 - 添加了适用于 Visual Studio 2008, 2010 的 64 位项目
  • 2013 年 1 月 29 日 - 改进了异常支持,添加了一个包装 ICard 的 WCF 服务 
  • 2013 年 2 月 20 日 - 浏览量达到 500,000 次。 
  • 2015 年 5 月 15 日 - 修复了一些问题,并添加了对 Mifare Classic 卡的支持。请从 Github 获取代码:https://github.com/orouit/SmartcardFramework.git
© . All rights reserved.