程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> 關於C++ >> 智能存儲:通過托管代碼和Windows Vista智能卡API來保護您的數據

智能存儲:通過托管代碼和Windows Vista智能卡API來保護您的數據

編輯:關於C++

本文討論:

Windows 智能卡編程基礎

示例智能卡應用程序的實現方法

編寫實現智能卡功能的托管打包程序

智能卡事務管理

這篇文章基 於 Windows Vista 的預發布版而撰寫。其中包含的信息可能會有所變動。

本文使 用了以下技術:

Windows Vista, C++, C#

目錄

Windows 智能卡編程

智能卡的發展

示例應用程序的實現方法

WinSCard API 打包程序

GetSmartCard 幫助器例程

卡模塊 API 打 包程序

處理 CardAcquireContext

事務管理

卡模塊接口設計基本原 理

使用 CLR 加密

依賴關系和測試

智能卡(簡單地說,就是嵌入了 微型芯片的信用卡)的概念已經提出將近 30 年了。但現在安全工作的重點是讓公司和政 府等機構重新審視一些早已有之的理念。

對於身份驗證系統的脆弱連接(即密碼 )來說,智能卡是一個很吸引人的替代方法。整個行業非常需要能夠替代密碼的技術。借 助嵌入式的加密處理器,智能卡提供了非常安全且易於使用的身份驗證機制。

然 而,智能卡的部署也帶來了其特有的挑戰。整個行業需要更好的產品來部署和管理復雜的 身份驗證技術。比爾·蓋茨在 RSA 2006 大會上做的主題發言中,演示了 Microsoft Certificate Lifecycle Manager,該產品充分利用了我們這篇文章中討論的 API。

整個行業還要著手應對客戶隱私保護的要求。消費者需要一種保護特權信息 的方法,例如,美國的在線銀行業很快會要求使用嚴格的身份驗證。在這個過程中,消費 者將使用諸如 Windows® CardSpace 的技術選擇在各種在線事務中,披露哪些個人信 息。例如,在在線銀行事務過程中,我可能很機密地證明我的社會保險號 (SSN) 與我的 身份是綁定在一起的,但我不可能與之共享我的信用卡號碼。反之,我會授權將我的信用 卡號碼(而不是 SSN)透露給電子商務網站。

Microsoft 已經認識到智能卡在其 平台的安全戰略中所扮演的重要角色。開發人員需要了解可識別智能卡的應用程序的工作 原理,以及 Windows 操作系統使用了何種方法,使生活變得更加輕松。

Windows 智能卡編程

Windows 已支持個人計算機/智能卡 (PC/SC) 軟件堆棧將近十年了( 詳細信息,請參見 www.pcscworkgroup.com)。PC/SC 功能通過 Windows 智能卡 (WinSCard) 客戶端 API 提供給應用程序,該 API 在 winscard.dll 和 scarddlg.dll 中實現(在後者中實現程度較小)。客戶端 API 允許主機應用程序通過智能卡資源管理 器服務(也稱作 SCardSvr)與智能卡間接進行通信。

由於智能卡是一種共享資源 ,在任何給定時間都可能有多個應用程序試圖與該卡進行通信,因此要使用資源管理器模 型。與該卡的通信必須是串行的。因此,資源管理器強制采用簡化的、類似於數據庫的鎖 定模型。在 PC/SC 中,持有給定智能卡讀卡器(也稱為接口設備或 IFD)的鎖,就等於 掌握了事務。然而,本文中“事務”這個術語可能會引起誤解。例如,沒有回 滾或取消提交的事情發生,PC/SC 不能自動撤銷應用程序在事務中作出的更改。

在 SCardEstablishContext、SCardBeginTransaction 以及其他 WinSCard 函數的平台 SDK 文檔中可找到對 PC/SC 的其他說明。主機應用程序使用這些例程建立到資源管理器 的管道,獲得智能卡讀卡器的獨占鎖,向卡發送一系列命令,然後釋放鎖。

圖 1 中匯總了 PC/SC 組件堆棧。請注意,PC/SC 客戶端代碼是在應用程序進程中加載的。資 源管理器是一種系統服務,是獨立於客戶端的。讀卡器設備驅動程序是唯一在內核模式下 運行的 PC/SC 堆棧組件。讀卡器驅動程序被資源管理器獨占加載,以防止應用程序忽略 事務模型。這是 PC/SC 安全模型很重要的一個方面。最後,本文中討論的 API 只供用戶 模式調用程序使用。遺憾的是,沒有內核模式的資源管理器。

圖 1PC/SC 組件堆棧

文章的剩余部分將重點討論棧頂:主機應用程序和 PC/SC 客戶 端 API。接著,我將討論此編程模型的局限性及其發展過程。

智能卡的發展

迄今為止,智能卡在 Windows 中的典型用途通常與身份驗證相關。例如,它們可 以代替密碼進行登錄。這些智能卡身份驗證方案是基於加密技術的。這就導致一直以來, 在 Windows 中添加對新型智能卡的支持始終要求供應商部署一個名為 CSP(加密服務提 供商)的插件,該插件部署了名為 Crypto API 1.0 的接口。不幸的是,人人都知道 CSP 的編寫工作非常復雜。而且,由於 CSP 接口是專用於低級別加密操作的,因此不能提供 現代智能卡的各種豐富的新功能。這些功能包括增加的數據存儲容量、高帶寬 I/O、卡載 小程序以及新的加密算法。由於缺少可實現這些功能的應用程序級別的編程模型,阻礙了 硬件供應商生產出有別於人的商品,也阻礙著編程人員為各種硬件編寫程序。

值 得高興的是,由於采用了被稱作“卡模塊 API”的新插件模型,Windows Vista™ 中的情況已大大改善。新 API 最大的優點是允許可識別智能卡的應用程序 不知道卡的具體類型。讓我們看一個示例。

我的示例是一個 Windows 數字權限管 理 (DRM) 應用程序,該程序使用智能卡來存儲數字媒體許可證。數字許可證包括數字簽 名的、經過壓縮的 XML Blob。通過在許可證生命周期中與智能卡的交互,主機應用程序 在卡上執行一些簡單的命令:創建存儲許可證的文件、寫入文件、讀取文件和刪除文件。 每個命令都對應於一個不同的字節序列。例如,如果應用程序確定被智能卡識別為 “02”的文件(很多智能卡的文件系統非常簡單)包含了許可證 Blob,那麼 可以對相關的命令/字節序列進行編碼:“讀取 02 文件的前 128 個字節。” 每個命令都是通過 WinSCard 函數 SCardTransmit 發送到卡上的。稍後,我將轉回到這 個話題。

要普及可識別智能卡的應用程序,需要注意的一個問題是各個供應商使 用的命令編碼是不同的。就算已經有了一些標准化編碼(如 ISO 7816),並且針對基本 操作(如讀取文件)的 ISO 編碼可能的確適用於多種卡,但通常開發人員不能指望這些 編碼。因此,必須對本示例中的 DRM 應用程序進行修改,以支持各種新的智能卡類型。 根據我的經驗,構建這些智能卡命令字節序列的應用程序代碼非常雜亂,很難維護。

新的卡模塊 API 通過提供與常見文件系統類似的接口,處理卡不兼容的問題,並 使用其他一些例程滿足前面提到過的與加密有關的身份驗證要求。例如,如果應用程序采 用的是針對各種卡進行“讀取 02 文件的前 128 個字節”命令編碼的舊模式 ,則偽代碼可能與圖 2 類似。

Figure2Pseudocode for Per-Card-Type Command Handling

if (cardType == A) {
  // Build read-file transmit buffer for card type A
  ...
  // Send the command to the card
   SCardTransmit(
    cardHandle,     // PC/SC context handle
    pciInputPointerA,  // Protocol Control Information (in)
     transmitBufferA,   // Our read-file command
     transmitBufferLength,// Read-file command length
     pciOutputPointerA,  // Protocol Control Information (out)
     receiveBuffer,    // Data response from card
     receiveBufferLength);// Length of data response
  // Interpret the response from this card type
  ...
}
else if (cardType == B) {
  // Build read-file transmit buffer for card type A
  ...
  // Send the command to the card
  SCardTransmit(
     cardHandle,     // PC/SC context handle
    pciInputPointerB,   // Protocol Control Information (in)
    transmitBufferB,   // Our read-file command
    transmitBufferLength,// Read-file command length
    pciOutputPointerB,  // Protocol Control Information (out)
    receiveBuffer,    // Data response from card
     receiveBufferLength);// Length of data response
  // Interpret the response from this card type
  ...
}

相反,如果使用新的 API,就可以通過調用單個函數來代替整個塊:

CardReadFile(
contextPointer, // PC/SC context handles
NULL,      // Directory in which requested file is located
"license",    // File name to read
&dataOut,    // Contents of the requested file
&dataLengthOut); // Size of the file contents
如果您覺得新的 API 比舊方法簡單很多,我完全同意。但別著急,還有一些工作要做!

到現在為止,我已 經描述了 PC/SC 軟件堆棧和卡模塊 API 的一些基本情況。我已經做好解決 DRM 應用程 序示例代碼的准備。由於我描述的智能卡相關接口都不向 Microsoft® .NET Framework 代碼開放,如果我能將卡模塊 API 的功能與 .NET 的方便之處結合起來,是 不是很酷?

我發現可采用最常規的解決方案來提供代碼打包程序,該程序可通過 P/Invoke(一種機制,用於調用來自托管代碼的本機 DLL 輸出)實現所需的 WinSCard 和卡模塊例程。然後,我可以用托管代碼演示整個應用程序。我發現如果編寫其他打包程 序代碼來簡化任務,可以使事情簡單一點。以前,如果不熟悉本機 API 的知識,實現托 管接口是非常困難的。

首先,我將概述示例應用程序需要做什麼。然後詳細討論 打包程序和 P/Invoke 接口。

示例應用程序的實現方法

首先,應用程序要 找到並綁定到一個插入的智能卡。如果未插入智能卡,程序將提示用戶插入一個智能卡。 實現這些操作需要用到 WinSCard API,因此這一部分的應用程序對 P/Invoke 和本機打 包程序非常依賴。

然後,我將打開卡上的加密密鑰對的句柄。如何沒有合適的密 鑰對,我將新建一個。幸運的是,所需的與加密相關的功能已經通過 CLR 提供了,因此 一切都很簡單。對於應用程序的加密部分,有一個潛在的障礙:必須安裝新版本的基本智 能卡加密服務提供程序 (Base Smart Card Crypto Service Provider),這是與基於卡模 塊的卡交換數據最可靠的方法。

我將創建一些代表數字媒體許可證的示例 XML 數 據。然後,我將使用加密密鑰對對數據進行加密。同樣,必需的 XML 和與加密相關的功 能已通過 CLR 提供。我將通過卡模塊保存數據和簽名。這需要新的本機打包程序和用於 本機例程的 P/Invoke 接口。下一步,既然該程序是用於測試的,我將反向執行前面的步 驟。這就是說,我將把剛剛寫入卡的文件讀取出來。最後,我將通過 CLR 加密例程對數 據進行解密。

我已經概要介紹了要在應用程序中看到的行為,現在讓我們將各種 功能要求分類。以下三個組成部分是所有我想要的東西:用於 WinSCard API 的打包程序 、用於卡模塊 API 的打包程序以及現有的 CLR 加密例程。一旦實現了這些操作並理解了 其中的道理,編寫應用程序這件事本身是很容易的!讓我們仔細了解各個組成部分。我建 議您下載示例 C# 代碼並認真學習,這些代碼中加入了一些關於智能卡編程基礎的注釋, 增加了趣味性。

WinSCard API 打包程序

該應用程序做的第一件事情是調 用 .NET 加密 API,找到合適的密鑰對。但是,我將在介紹完該應用程序進行的底層智能 卡 API 調用之後再討論這部分問題,以闡明我對整體體系架構的看法。

為了通過 卡模塊 API 與智能卡直接進行交互,我要做的第一件事就是為適當的卡獲取 PC/SC 句柄 。(請注意,此處引述的所有本機例程在 Platform SDK 中都有記錄,因此我不會在此為 用到的各個公共例程提供詳細的使用方法。)為了綁定到來自托管應用程序的智能卡,我 需要介紹幾個本機 API。

任何 Windows 智能卡應用程序的基礎都是 SCardEstablishContext。從 winscard.dll 輸出的 PC/SC 例程建立了句柄,該句柄允許 應用程序與智能卡資源管理器交互。其功能非常簡單,對於這些簡單的功能,無需實現額 外的本機打包程序。P/Invoke 接口足以:

[DllImport ("winscard.dll")]
public static extern Int32 SCardEstablishContext(
UInt32 scope,
[MarshalAs (UnmanagedType.AsAny)] Object reserved1,
[MarshalAs(UnmanagedType.AsAny)] Object reserved2,
ref IntPtr pcscContext);

示例應用程序運用的第 二個 PC/SC 例程 SCardUIDlgSelectCardW 是目前為止最難在 .NET 代碼中使用的例程。 這有兩個原因。第一個原因,此函數使用了單個 C 樣式結構作為其參數。(請參見平台 SDK 中 winscard.h 定義的 OPENCARDNAME_EXW。)該結構除了包括若干個其他字段外, 還包括兩個充當輸入和輸出的 Unicode 字符數組。要正確地排定這個結構是要費些周折 的。需要說明的是,這個結構包含一個可選的嵌入式結構:OPENCARD_SEARCH_CRITERIAW 。該結構也是在 winscard.h 中定義的。這個嵌套結構不比第一個結構簡單。對於這個示 例而言,我選擇不采用依賴於第二個結構的 SCardUIDlgSelectCardW 的高級功能。

采用 SCardUIDlgSelectCardW 所面臨的第二個挑戰是,必須將 OPENCARDNAME_EXW 第一個數據成員初始化,使之與結構大小相同(以字節為單位)。我 嘗試了三種方法,試圖將該字段初始化為正確的值。第一種方法,我試圖對其進行硬編碼 — 這就是說,確定該結構的大小(例如,通過調試程序或采用本機代碼的簡單測試 應用程序),始終在托管代碼中將該字段初始化為該值。使用易於移植的代碼是很不錯的 ,但是,在 64 位的環境下,該結構的大小會有所不同。

第二種方法,您可以使 用一個或多個 CLR 編排原始字節(如 Marshal.SizeOf)來確定被編排的結構在運行時的 大小。不幸的是,在我的測試中,該特殊例程得到了令人意外的結果,表明特定數據成員 經編排後的大小是無法確定的。而且,除非此方法得到的大小與本機大小是完全一樣的, 否則,這不是一個好主意。

第三種方法,我實現了一個可接受部分初始化結構的 本機打包程序,設置 dwStructSize 字段,然後調用 SCardUIDlgSelectCardW。然後,我 通過 P/Invoke 向托管代碼提供了本機打包程序。您可能已經猜出來了,我選擇了這種做 法。我知道,出於靈活性的考慮,將本機打包程序封裝為獨立的 DLL 以及添加額外 DLL 的管理開銷以支持單個 API 的做法令人很難接受,但我已經獲知卡模塊例程也需要類似 的解決方案。換言之,我發現提供一個獨立的本機 DLL 是不可避免的。我將在下一節介 紹卡模塊例程。

在示例代碼中,MgSc.dll(托管智能卡的縮寫)輸出本機打包程 序例程。打包程序例程的代碼(包括 MgScSCardUIDlgSelectCardW)存儲在 MgSc.cpp 中 。該 DLL 中用於所有本機打包程序的 P/Invoke 類也稱作 MgSc;它包含與以下內容類似 的 P/Invoke 占位程序:

[DllImport("mgsc.dll", CharSet=CharSet.Unicode)]
public static extern Int32 MgScSCardUIDlgSelectCardW(
  [MarshalAs(UnmanagedType.LPStruct)] [In, Out]
  PCSCOpenCardNameWEx ocnwex);

讓該 API 正常工作後,其他 工作就顯得非常簡單了!

GetSmartCard 幫助器例程

圖 3 顯示了摘錄的示 例代碼中的 GetSmartCard 幫助器例程。請注意,如果 MgScSCardUIDlgSelectCardW 是 成功的,則意味著 PCSCOpenCardNameWEx 參數的 cardHandle 成員被初始化並對應於一 個已插入的智能卡。換言之,我們已經可以使用這張卡了。

Figure3GetSmartCard Helper Routine

static Int32 GetSmartCard(
  [In, Out] ref MgScContext mgscContext,
  [In, Out] ref IntPtr pcscContext,
  [In, Out] ref IntPtr cardHandle)
{
  Int32 result = 0;
  PCSCOpenCardNameWEx ocnwex = new PCSCOpenCardNameWEx ();
  bool cardIsLocked = false;
  string readerName;
   UInt32 readerNameLength = 0, cardState = 0, cardProtocol = 0;
  byte [] atr;
  UInt32 atrLength = 0;
  try
  {
    // Get a context handle from the smart card resource manager
    if (0 != (result = PCSC.SCardEstablishContext(
       PCSCScope.SCARD_SCOPE_USER, null, null, ref pcscContext)))
         throw new Exception("SCardEstablishContext");
    // Get a handle to a card, prompting the user if necessary
    ocnwex.flags = PCSCDialogUI.SC_DLG_MINIMAL_UI;
    ocnwex.pcscContext = pcscContext;
    ocnwex.shareMode = PCSCShareMode.SCARD_SHARE_SHARED;
    ocnwex.preferredProtocols = PCSCProtocol.SCARD_PROTOCOL_Tx;
    ocnwex.reader = new string(
      (char) 0, (int) readerAndCardBufferLength);
     ocnwex.maxReaderLength = readerAndCardBufferLength;
    ocnwex.card = new string(
      (char) 0, (int) readerAndCardBufferLength);
    ocnwex.maxCardLength = readerAndCardBufferLength;
    if (0 != (result = MgSc.MgScSCardUIDlgSelectCardW(ocnwex)))
      throw new Exception("SCardUIDlgSelectCardW");
    // Lock the card
    if (0 != (result = PCSC.SCardBeginTransaction (ocnwex.cardHandle)))
      throw new Exception ("SCardBeginTransaction");
    cardIsLocked = true;
     // Get the ATR for the selected card
    if (0 != (result = PCSC.SCardStatusW(ocnwex.cardHandle, null,
      ref readerNameLength, ref cardState, ref cardProtocol,
      null, ref atrLength)))
        throw new Exception ("SCardStatusW");
    readerName = new string((char) 0, (int) readerNameLength);
    atr = new byte [atrLength];
     if (0 != (result = PCSC.SCardStatusW(ocnwex.cardHandle,
       readerName, ref readerNameLength, ref cardState,
      ref cardProtocol, atr, ref atrLength)))
        throw new Exception ("SCardStatusW");
    // Get a card module handle for this card
    mgscContext = new MgScContext();
    if (0 != (result = (int) MgSc.MgScCardAcquireContext(
      mgscContext, pcscContext, ocnwex.cardHandle,
      ocnwex.card, atr, atrLength, 0)))
        throw new Exception ("MgScCardAcquireContext");
    cardHandle = ocnwex.cardHandle;
  }
  finally
  {
    if (0 != result)
    {
      // Something failed, so we need to cleanup.
      if (cardIsLocked)
         PCSC.SCardEndTransaction(ocnwex.cardHandle,
           PCSCDisposition.SCARD_LEAVE_CARD);
      if ((IntPtr)0 != ocnwex.cardHandle)
        PCSC.SCardDisconnect (ocnwex.cardHandle,
           PCSCDisposition.SCARD_LEAVE_CARD);
      if ((IntPtr)0 != ocnwex.pcscContext)
      {
         PCSC.SCardReleaseContext(ocnwex.pcscContext);
        pcscContext = (IntPtr)0;
      }
    }
  }
  return result;
}

我使用返回的卡句柄在 GetSmartCard 中做的第一件事情是調 用 SCardBeginTransaction。這一操作授予了對智能卡的獨占訪問權限,防止其他應用程 序(甚至該進程中的其他卡句柄)與該卡交互。如果 GetSmartCard 例程成功返回,事務 仍然是保留的,我將使卡保持在鎖定狀態,直到調用托管的加密 API 例程為止。在那個 時間點釋放鎖的原因很深奧,我將稍後作出解釋。

我對卡句柄做的第二件事情是 用句柄查詢用於所選智能卡的 ATR(重置應答)字符串的 PC/SC。ATR 的字節組成了一個 因卡類型而異的標識符,該標識符簡化了卡與智能卡讀卡器設備驅動程序之間的底層協議 協商。也可以使用卡 ATR,通過系統注冊表,在卡和對應的卡模塊之間實現映射(還可以 在卡與對應的 CSP 之間,盡管這些數據是由加密 API 提取的)。換言之,為了加載正確 的卡模塊,我必須首先知道要與之交換信息的卡的 ATR。

既然我已經有了卡句柄 ,就可以通過 P/Invoke 調用 SCardStatus API 以獲取 ATR 了。您可以在 GetSmartCard 中看到我調用了兩次 SCardStatus:一次是查詢兩個變量長度輸出參數的 大小,第二次是用足夠大小的緩沖區接受輸出信息。但如果您看一下針對 SCardStatus 的平台 SDK 文檔,會發現 API 還可以以調用程序的名義分配輸出緩沖區(可選)。

以 ATR 輸出緩沖區為例。在本機代碼中,調用程序可以將緩沖區長度的輸入/輸 出參數 pcbAtrLen 設置為指向 SCARD_AUTOALLOCATE 值,該值在 winscard.h 中定義為 –1。然後,pbAtr 參數被作為 LPBYTE * 傳送並轉換為 LPBYTE。返回的緩沖區通 過 SCcardFreeMemory 釋放。很多 PC/SC 函數也使用同樣的語義。實際上,每當我編寫 與 PC/SC 相關的本機代碼時,都會出於方便的目的,頻繁使用 SCARD_AUTOALLOCATE。然 而,由於定義如此毫無規律的行為和正確編排這些行為的復雜性,我沒有選擇在打包程序 中采用這一功能。

一旦獲取了 ATR,我就會將選定的卡映射到卡模塊。我決定將 操作提取到打包程序函數(我所實現的將卡模塊提供給托管代碼的那些函數)之一中。這 是下一節的主題。

卡模塊 API 打包程序

本機卡模塊接口是在 cardmod.h 中定義的,Windows Vista 平台 SDK 的最新版本中提供了這一文件。如果您看一下在頭 文件尾部定義的 CARD_DATA 結構,就會注意到,在 .NET 代碼中采用卡模塊例程會有一 些復雜化。與某些 PC/SC 例程中由 SCARD_AUTOALLOCATE 帶來的有關內存管理的挑戰相 似,一些卡模塊例程以調用程序的名義分配動態大小的輸出緩沖區。但是,與 SCARD_AUTOALLOCATE 不同的是,卡模塊內存分配行為不是可選的。以 CardReadFile 為 例(當我從智能卡讀取數字許可證文件時,需要在示例應用程序中使用該文件)。我將通 過函數指針終止調用 CardReadFile,指針類型按如下方式定義。

typedef DWORD (WINAPI *PFN_CARD_READ_FILE)(
__in            PCARD_DATA  pCardData,
__in            LPSTR pszDirectoryName,
__in             LPSTR pszFileName,
__in            DWORD dwFlags,
__out_bcount(*pcbData)   PBYTE *ppbData,
__out            PDWORD pcbData);

現在看一下 CARD_DATA 結構的兩個成 員。第一個成員是創建輸出緩沖區時,卡模塊使用的堆分配器回調。第二個成員是在 CardAcquireContext 執行期間由卡模塊初始化的 PFN_CARD_READ_FILE 函數指針,供後 面的調用應用程序使用。

// These members must be initialized by the CSP/KSP before
// calling CardAcquireContext.
...
PFN_CSP_ALLOC       pfnCspAlloc;
PFN_CSP_FREE      pfnCspFree;
...
// These members are initialized by the card module
...
PFN_CARD_READ_FILE   pfnCardReadFile;

總之,在 CardReadFile 調用 過程中,卡模塊或多或少會執行以下一些操作:通過 pfnCspAlloc 分配足夠大的緩沖區 、通過 SCardTransmit 從卡中讀取請求的文件、設置 pcbData 並返回。一旦調用程序結 束並返回數據緩沖區,則調用 pfnCspFree。

我知道,與手工編寫一組 P/Invokes 相比,為每個卡模塊例程使用一個本機打包程序會縮短時間。例如,在調用 CardReadFile 時,.NET 應用程序調用一次本地打包程序,確定輸出緩沖區的大小,然後 用足夠大小的緩沖區再次調用。這很明顯是一個性能上的折衷,因為應用程序從智能卡上 讀取兩次文件。這種情況很有可能通過為堆回調實現委托得以改善。

處理 CardAcquireContext

讓我們回到示例數字許可證應用程序的思路中。回想一下, 我通過 PC/SC 例程,獲取了卡以及該卡的 ATR。基於該 ATR,我在稱作 Calais 的數據 庫中查找對應卡模塊 DLL 的名稱,該名稱就是存儲在系統注冊表HKLM\Software\Microsoft\Cryptography\Calais中的數據。如果 PC/SC 堆棧最初是在 Microsoft 實現的,則將“Calais”選為項目代碼名稱。根據某些流傳的說法 ,我想智能卡是由法國人發明並不完全是巧合。

Calais 數據庫查找是通過 SCardGetCardTypeProviderName 傳送 cardmod.h 中定義的 SCARD_PROVIDER_CARD_MODULE 值完成的。我決定將其滾入到 MgScCardAcquireContext, 即 CardAcquireContext 的本機代碼打包程序,而不是通過 P/Invoke 接口,將特定的 PC/SC 例程提供給托管環境。為什麼呢?讓我們向下看幾步(本機代碼)操作。下一步是 獲得由 Calais 數據庫返回的 DLL 字符串名稱並將其傳遞給 LoadLibrary。必須在整個 上下文的生命周期中,用卡模塊維護得到的 HMODULE(我當然不希望卸載該卡模塊)。然 後調用 GetProcAddress 找到 CardAcquireContext 輸出。最後,我用卡模塊期待的本機 回調(包括堆分配例程)初始化 CARD_DATA 結構。

總之,如果 .NET 代碼僅在准 備調用 CardAcquireContext 時執行,則通過 .NET 代碼讓所有與本機代碼相關的材料運 行起來,不會有什麼好的結果。將其滾動到該例程的本機打包程序會好一些。

請 考慮卡模塊實際執行了什麼操作。給定的卡模塊處理 CardAcquireContext 時的步驟取決 於卡的類型。如果該卡基於 ISO 命令集,則此時卡模塊無需向卡發送任何命令。相反, 卡模塊將確認是否的確支持指定的 ATR(通過 CARD_DATA 由調用程序輸入)。然後可能 設置一些內部上下文狀態並將其附加到調用程序的 CARD_DATA 的 pvVendorSpecific 成 員。另一方面,較新的卡可能是基於代理或虛擬機的。Sun 的 Java 卡是一個很明顯的示 例。在這種情況下,卡模塊將很可能通過 SCardTransmit 向卡發送命令,將卡端卡模塊 API 處理程序 (card-side card module API handler) 小程序實例化。

事務管理

現在,讓我們回來討論示例代碼。如果 GetSmartCard 幫助器例程成功返回,則 卡上的事務仍處於被控制的狀態。換言之,GetSmartCard 輸出的卡句柄仍然對卡擁有獨 占的訪問權限。這麼做不僅僅是為了將一切呈現在您面前。請注意,示例應用程序的總體 流首先要通過托管的加密 API 對數據加密,然後執行所有必要的卡模塊工作,包括寫回 和讀取文件,最後通過加密 API 將數據解密。

操作的順序突出了我稱之為事務管 理的幾個需要注意的重要問題。第一個需要注意的問題是,在調用 PC/SC 和卡模塊例程 時,我希望在將獨立事務的數量降到最低的同時根據需要執行盡可能多的操作。換言之, 我要盡可能減少釋放和重新獲取與智能卡之間的獨占通信鏈接的次數。通過在單個事務完 成所有調用,消除了另外一個應用程序介入並使卡處於未知狀態的可能性。

然而 ,這個策略也只能有這麼大作用;第二個要注意的問題是加密 API 自己進行事務管理。 例如,我從卡讀出加密文件之後,在調用托管的加密 API 對文件進行解密之前,必須調 用 SCardEndTransaction。否則,對 RSACryptoServiceProvider.Decrypt 的調用將永遠 停止在深處 CSP 內部的 SCardBeginTransaction 上。

第三個需要注意的事務管 理問題(也是一個重要的安全問題)是,卡在進行身份驗證的過程中,必須保持鎖定狀態 。利用卡模塊接口,當正確的 PIN 通過 CardAuthenticatePin 傳送後,卡便通過了身份 驗證。我已經通過 MgScCardAuthenticatePin 的 P/Invoke 接口實現這一操作。PC/SC 安全模型是使用以下順序對卡執行權限操作的應用程序。

通過 SCardBeginTransaction 鎖定卡。

對卡進行身份驗證。我使用 CardAuthenticatePin 完成此操作。

執行權限操作。在此示例中,我首先嘗試從 卡中刪除一個文件,然後嘗試寫入一個文件。兩個操作都要求進行身份驗證。

取 消對卡的身份驗證。

通過 SCardBeginTransaction 解鎖卡。

這樣,未經 授權的應用程序不能向由已授權應用程序驗證過的卡發送權限命令。

某些卡不支 持直接取消身份驗證。應用程序還必須通過 PC/SC 重置卡。重置使卡回到最初的、未經 身份驗證的狀態。雖然重置有效地“重新啟動”了智能卡,但是這會對下一個 嘗試與卡進行通信的主機應用程序的性能帶來負面影響。因此,卡模塊接口在設計時采用 了可選的例程,避免了這一問題以及由此帶來的性能上的缺陷。

在本機打包程序 函數 MgScCardDeauthenticate 中,如果目標卡模塊沒有實現 CardDeauthenticate 函數 ,打包程序將返回 ERROR_CALL_NOT_IMPLEMENTED。如果取消身份驗證的調用由於某種原 因失敗了,托管應用程序將通知自己在釋放當前事務前,必須重置該卡。

if (0 != (result = (Int32) MgSc.MgScCardDeauthenticate(
   mgscContext, MgScUserId.User, 0)))
    cardDispositionOnRelease = PCSCDisposition.SCARD_RESET_CARD;
這與我之前描述的事務管理安全模型是一致 的。

卡模塊接口設計基本原理

應用程序與智能卡之間交互的最終細節是最 簡單的,上文已概要介紹。那就是,要實現將數字許可證文件放置在卡上的與存儲相關的 邏輯,首先通過 P/Invoke 調用本機 MgScCardDeleteFile。出於簡便的考慮,我沒有檢 查返回的值,這是因為,如果這是第一次對文件不存在的卡執行該應用程序,API 將返回 一個錯誤。

下一步,調用 CreateFile 和 WriteFile 的打包程序。我們一起來學 習卡模塊 API 的設計基本原理,這非常有趣。獨立的 CardCreateFile 和 CardWriteFile 例程提供了復制 CREATE_NEW 和 OPEN_EXISTING 語義的簡單方法,語義 包括相應的錯誤條件,由人們熟悉的 Windows CreateFile API 提供。這一點非常重要, 由於編程人員可以使用在文件 I/O 中大體掌握的模式與卡進行交互,因此這樣做降低了 編程人員工作的難度。

但與 Windows CreateFile 函數不同,CardCreateFile 將 字節計數參數作為輸入。有些卡模塊會忽略該參數,但應用程序傳入文件的大小最好不超 過允許創建的文件的最大大小的底限。這麼做的原因是因為有些卡必須在創建時預先分配 整個文件。另一方面,您也不想浪費空間。這一設計中一個可能令人困惑的問題是, CardReadFile 可能返回文件的整體分配空間的大小(傳遞給 CardCreateFile 的可能較 大的值),而不僅僅是通過 CardWriteFile 寫入的可能較小的值。

將加密的許可 證寫入卡之後,我通過 MgScCardReadFile 讀取它。正式的應用程序不執行這一步驟,我 這麼做是出於說明問題和測試接口的目的。接下來的步驟是解密文件,將其與原始值比較 ,進行完整性檢查。

使用 CLR 加密

請注意,我在示例代碼中做的第一件 事是通過 .NET 加密 API 綁定到基於智能卡的 RSA 密鑰對。(參見 System.Security.Cryptography 命名空間中 RSACryptoServiceProvider 類的文檔。)

盡管 .NET 提供的抽象技術和強大的功能很不錯,但我總是想要看看具體發生了 什麼。由於 .NET 加密 API 是基於本機加密 API 的,我可以對此代碼做一些假定(代碼 是從 GetKey 幫助器例程改寫而來的):

CspParameters cspParameters = new CspParameters(
  1, "Microsoft Base Smart Card Crypto Provider");
cspParameters.Flags = CspProviderFlags.UseExistingKey;
cspParameters.KeyContainerName = "";
rsa = new RSACryptoServiceProvider (cspParameters);

我可以假定此代碼至少應調用 CryptAcquireContext 和 CryptGetUserKey。如果這樣不行,則將 cspParameters.Flags 設置為零並再次嘗試。這 將導致在調用 CryptAcquireContext 後,調用 CryptGenKey。

依賴關系和測試

我可以進一步做出 CSP 實際在執行什麼操作的斷言。這將易於創建示例應用程序 的組件依賴關系圖。我調用的 CSP 實際是在卡模塊接口上構建的;這是首選的寫入卡的 主要基本原理。而它是智能卡 CSP 這一事實暗示,它可能在直接調用卡模塊例程之前, 使用與示例代碼類似的方式,建立到智能卡的連接。

換言之,在對上文討論過的 SCardUIDlgSelectCardW、SCardBeginTransaction、SCardStatus 和 CardAcquireContext 進行顯式調用前,CSP 或多或少也在內部進行同樣的調用。特別是 考慮到僅為調用 CardAcquireContext 而需要進行設置的工作量,這種重復工作顯然不受 歡迎。如果對卡模塊進行直接調用的上下文信息可以從加密 API 檢索或至少通過某些共 享代碼公布這些信息,那就更好了。

圖 4 描述了這些依賴關系。圖的底層代表來 自 winscard.dll 客戶端 API 智能卡堆棧中的所有內容,上至資源管理器服務,下至驅 動程序,以及智能卡。

圖 4 程序樣品的依賴關系

我已經詳細討論了一些智能卡的基礎知識和測試應用程序, 我希望您下載示例代碼並體驗一下。要構建和運行示例代碼需要幾項特定的內容:

最新版本的 Windows Vista 平台 SDK 中的 cardmod.h 頭文件。

基於卡 模塊的智能卡。即已由供應商提供了卡模塊的智能卡。

智能卡讀卡器。在某些情 況下,諸如特定的 USB 密鑰、智能卡和讀卡器等是一體的。

基本智能卡加密提供 程序 (Base Smart Card Crypto Provider)。可通過 Windows Update 下載。單擊 “自定義”按鈕,並選擇左側的“軟件”、“可選”。

Code download available at: SmartCards2006_11.exe (2079 KB)

http://download.microsoft.com/download/f/2/7/f279e71e-efb0-4155-873d- 5554a0608523/SmartCards2006_11.exe

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved