暴露智能卡核心功能的 WCF 服务
本文介绍了一个 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); }
APDUCommand
和 APDUResponse
类在 WCF 服务中被重新定义为 DataContract
,并且与我在原始智能卡框架项目中开发的原始 APDUCommand
和 APDUResponse
类不同。它们只是没有方法的 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 实现中的一个潜在问题。