The Will Will Web

記載著 Will 在網路世界的學習心得與技術分享

CryptographicException: 控制碼無效(The handle is invalid)

我前天寫的【ASP.NET 使用 X509Certificate2 類別匯入憑證檔時發生錯誤 】文章是說明有關於匯入憑證時發生的問題,雖然已經可以匯入憑證到本機電腦(Local Computer)但這並不代表你可以使用該憑證進行簽章或加解密的動作,運氣不好的話你會得到 控制碼無效The handle is invalid 的錯誤。

由於我的目的是要利用憑證對我的訊息作簽章動作,範例的程式碼如下:

X509Store store = new X509Store("My", StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly);

System.Security.Cryptography.X509Certificates.X509Certificate2 cert  = 
    store.Certificates.Find(X509FindType.FindBySubjectName, "MyCertificate", false)[0];

// 先將文字訊息轉成 byte[] 陣列
byte[] data = Encoding.GetEncoding("Big5").GetBytes("xxxxx");

SignedCms signedCms = new SignedCms(new ContentInfo(data), false);
CmsSigner signer = new CmsSigner(SubjectIdentifierType.IssuerAndSerialNumber, signCert);
// 用指定的簽署人建立簽章,並將簽章加入至 CMS/PKCS #7 訊息 ( 這行會出錯! )
signedCms.ComputeSignature(signer);
byte[] signedbytes = signedCms.Encode();

錯誤發生時的 Stack Trace 的內容如下:

[CryptographicException: The handle is invalid.]
   System.Security.Cryptography.Pkcs.PkcsUtils.CreateSignerEncodeInfo(CmsSigner signer, Boolean silent) +190432
   System.Security.Cryptography.Pkcs.SignedCms.Sign(CmsSigner signer, Boolean silent) +134
   System.Security.Cryptography.Pkcs.SignedCms.ComputeSignature(CmsSigner signer, Boolean silent) +345
   System.Security.Cryptography.Pkcs.SignedCms.ComputeSignature(CmsSigner signer) +6
   test.btnSaveSetting_Click(Object sender, EventArgs e) in g:\WebRoot\Test.aspx.cs:1072
   System.Web.UI.WebControls.LinkButton.OnClick(EventArgs e) +111
   System.Web.UI.WebControls.LinkButton.RaisePostBackEvent(String eventArgument) +79
   System.Web.UI.WebControls.LinkButton.System.Web.UI.IPostBackEventHandler.RaisePostBackEvent(String eventArgument) +10
   System.Web.UI.Page.RaisePostBackEvent(IPostBackEventHandler sourceControl, String eventArgument) +13
   System.Web.UI.Page.RaisePostBackEvent(NameValueCollection postData) +175
   System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint

問題原因

經研究後發現此問題發生的原因是因為處理簽章動作時需要讀取憑證的私鑰檔(Private Key File),而 ASP.NET 使用的身份識別又沒有 Private Key 的讀取權限所致。

解決辦法

從這篇 KB278381 得知,放在本機電腦的 Private Key 都會放在以下這個目錄裡:

C:\Documents and Settings\all users\Application Data\Microsoft\Crypto\RSA\MachineKeys

C:\Documents and Settings\All Users\Application Data\Microsoft\Crypto\RSA\MachineKeys

預設來說,雖然在 MachineKeys 目錄已經對 Everyone 設定了所有可讀、可寫的權限:

在 MachineKeys 目錄已經對 Everyone 設定了所有可讀、可寫的權限

但實際上在目錄內的每一個 Private Key 檔案都沒有繼承目錄的權限設定,大多都只有 SYSTEM本機管理者的權限有開放而已,所以使用 ASP.NET 當然沒辦法讀取與使用這些憑證。

基於安全性考量,我們不希望所有憑證檔都可以讓 ASP.NET 讀取,所以我們就要找出我們之前匯入到 憑證 (本機電腦)\個人\憑證 路徑下的那張憑證對應的 Private Key 檔,並設定適當的權限給 ASP.NET 執行時的身份識別。

但是你可以發現在 MachineKeys 目錄下的檔案都是一堆亂碼與 GUID,透過 Windows 內建的工具並沒有辦法幫你對映出「哪張憑證」對應到「哪個 Private Key 檔」。要解決這個問題必須先下載 Web Services Enhancements (WSE) 3.0 for Microsoft .NET 工具回來安裝,然後開啟 Certificates Tool 工具。

Microsoft WSE 3.0 --> Certificates Tool

然後依照以下圖示選取正確的憑證出來:

使用 WSE X.509 Certificate Tool 選取憑證檔

接著透過 View Private Key File Properties... 按鈕將我們苦苦尋找的 Private Key File 的檔案屬性叫出來,並加入 IUSR_MachineName 的權限即可大功告成。

透過 View Private Key File Properties... 按鈕將我們苦苦尋找的 Private Key File 的檔案屬性叫出來,並加入 IUSR_MachineName 的權限

---

因為 .NET 組件在讀取 Private Key File 的部分是透過 COM 元件處理的,並非使用 Managed Code,所以這部分的讀取權限並不是使用 IIS 的應用程式集區身份識別進行讀取 ( IIS6 預設為 Network Service,IIS5 預設為 ASPNET ),而是採用 IIS 的匿名存取分身識別 ( 預設為 IUSR_MachineName ) 讀取檔案,因此必須要加入 IUSR_MachineName 的權限才行!

---

這原理同樣套用到其他非 ASP.NET 的執行環境,如果使用者沒有該憑證檔的讀取權限一樣會引發相同的例外狀況,只要照這篇說明的方式進行調整權限即可。但是第一次遇到這個問題的人實在很難用「控制碼無效」找到解決方案。 = =''

---

呼~~ 我真害怕碰觸這些複雜的「憑證」,由於沒有很完整的基礎知識,所以每次要處理這些東西總是十分費力,都要搞很久才可以弄清楚來龍去脈,然後這些技術又不會經常接觸,過了一段時間後可能又忘了,如果沒有這些紀錄我想我以後又會再重蹈覆轍一遍。

相關連結