﻿# ASP.NET 使用 X509Certificate2 類別匯入憑證檔時發生錯誤

我最近因為需要在 ASP.NET 網站發信，而且發出的電子郵件必須透過 S/MIME 憑證進行簽章，在本機測試的時候都沒有問題，但是一旦將程式部署到客戶端的主機時就會發生『系統找不到指定的檔案 (The system cannot find the file specified.)』的錯誤，檔案明明就有完整的讀取權限，但錯誤訊息卻是「系統找不到指定的檔案」？我已經被這個問題困擾好幾個星期了。

錯誤的程式碼如下：

```
X509Certificate2 cert = new X509Certificate2(Server.MapPath("~/App_Data/Test.pfx"), "Password");
```

而我取得的 Stack Trace 內容如下：

```
[CryptographicException: The system cannot find the file specified.]
   System.Security.Cryptography.CryptographicException.ThrowCryptogaphicException(Int32 hr) +33
   System.Security.Cryptography.X509Certificates.X509Utils._LoadCertFromFile(String fileName, IntPtr password, UInt32 dwFlags, Boolean persistKeySet, SafeCertContextHandle& pCertCtx) +0
   System.Security.Cryptography.X509Certificates.X509Certificate.LoadCertificateFromFile(String fileName, Object password, X509KeyStorageFlags keyStorageFlags) +237
   System.Security.Cryptography.X509Certificates.X509Certificate2..ctor(String fileName, String password) +131
   ASP.test_aspx.Page_Load(Object sender, EventArgs e) in g:\WebRoot\Test.aspx:16
   System.Web.Util.CalliHelper.EventArgFunctionCaller(IntPtr fp, Object o, Object t, EventArgs e) +14
   System.Web.Util.CalliEventHandlerDelegateProxy.Callback(Object sender, EventArgs e) +35
   System.Web.UI.Control.OnLoad(EventArgs e) +99
   System.Web.UI.Control.LoadRecursive() +50
   System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint) +627
```

最後我透過微軟的技術支援中心的協助下找到了 [KB948154](https://web.archive.org/web/20140321084817/http://support.microsoft.com:80/kb/948154) 這份 KB，也成功的解決無法載入憑證檔的問題，詳細問題發生原因文件裡都有說明我就不再贅述，以下分享解決問題的過程，一共三大步驟：

### 第一步：將PFX或P12憑證檔匯入本機電腦的憑證儲存區

將憑證匯入到**本機電腦的憑證儲存區**(Local Computer certificate store)是為了要讓憑證可以在本機電腦共用，這樣才可以讓 ASP.NET 正確載入憑證。

1\. 在 \[開始\] -> \[執行\] --> 輸入 mmc 執行「主控台」程式

2\. 按下 Ctrl + M 準備**新增嵌入式管理單元**

![按下 Ctrl + M 準備新增嵌入式管理單元](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/117b4b26c9c1fe00455e-image_3.png)

3\. 接著新增 \[憑證\] --> \[電腦帳戶\] --> \[本機電腦\] --> \[完成\]

![新增 \[憑證\] --> \[電腦帳戶\] --> \[本機電腦\] --> \[完成\]](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/f82a1e4ed9c3ea1eddb8-image_6.png)

4\. 然後準備匯入你無法載入的 PFX 憑證檔。\[選取檔案\] --> \[輸入密碼\] --> \[下一步\] --> \[完成\]

![準備匯入你無法載入的 PFX 憑證檔。\[選取檔案\] --> \[輸入密碼\] --> \[下一步\] --> \[完成\]](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/229f3841a57bdfef3b33-image_9.png)

5\. 匯入成功後，你就會在憑證清單的地方看到你剛剛匯入的憑證，其中的 \[發給\] 欄位所列的名稱你必須先記下來，後面的步驟會用到。

![匯入成功後，你就會在憑證清單的地方看到你剛剛匯入的憑證，其中的 \[發給\] 欄位所列的名稱你必須先記下來，後面的步驟會用到。](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/04a98e1539c4a6c7aec1-image_12.png)

### 第二步：授權憑證的使用權利給 ASP.NET 執行時的那個身份識別

ASP.NET 執行時的身份識別會定義在 IIS 的應用程式集區裡，預設值是「網路服務」，也就是 "**Network Service**" 帳戶。

1\. 先下載安裝 **[WinHttpCertCfg.exe](http://www.microsoft.com/downloads/details.aspx?familyid=c42e27ac-3409-40e9-8667-c748e422833f&displaylang=en)** 工具 ( Windows HTTP Services Certificate Configuration Tool )

2\. 安裝好之後，該工具會置於 **C:\\Program Files\\Windows Resource Kits\\Tools** 目錄下

3\. 以下指令範例的 "**MyCertificate**" 就是你的憑證名稱(subject name)，也就是上面那張圖的 \[發給\] 中的名稱，而 "Network Service" 就是要授權的帳號。

```
cd /d C:\Program Files\Windows Resource Kits\Tools
winhttpcertcfg -g -c LOCAL_MACHINE\MY -s "MyCertificate" -a "Network Service"
```

### 第三步：修改原本匯入憑證檔的程式碼

原本的程式碼如下：

```
X509Certificate2 cert = new X509Certificate2(Server.MapPath("~/App_Data/Test.pfx"), "Password");
```

因為憑證已經被匯入到**本機電腦的憑證儲存區**，所以必須修改成以下程式碼：

```
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];
```

以上程式碼唯一要注意的地方就是 "**MyCertificate**" 這個字串，要改成你的憑證名稱(subject name)，也就是上面那張圖的 \[發給\] 中的名稱。

**延伸閱讀**

-   [CryptographicException: 控制碼無效(The handle is invalid)](/post/2009/04/17/CryptographicException-The-handle-is-invalid)

**相關連結**

-   [Importing a PFX file using X509Certificate from ASP/ASP.NET or COM+ fails](https://web.archive.org/web/20140321084817/http://support.microsoft.com:80/kb/948154)
-   [WinHttpCertCfg.exe, a Certificate Configuration Tool](http://msdn.microsoft.com/en-us/library/aa384088.aspx)
-   [About Microsoft Windows HTTP Services (WinHTTP)](http://msdn.microsoft.com/en-us/library/aa382925\(VS.85\).aspx)
-   [下載：Windows HTTP Services Certificate Configuration Tool (WinHttpCertCfg.exe)](http://www.microsoft.com/downloads/details.aspx?familyid=c42e27ac-3409-40e9-8667-c748e422833f&displaylang=en)
-   [下載：Windows Server 2003 Resource Kit Tools](https://web.archive.org/web/20070902011448/http://www.microsoft.com/downloads/details.aspx?familyid=9d467a69-57ff-4ae7-96ee-b18c4790cffd&displaylang=en)
-   [X509Certificate2 Class](http://msdn.microsoft.com/en-us/library/system.security.cryptography.x509certificates.x509certificate2.aspx) / [X509Certificate2 類別](http://msdn.microsoft.com/zh-tw/library/system.security.cryptography.x509certificates.x509certificate2.aspx)
-   [X509Store Class](http://msdn.microsoft.com/en-us/library/system.security.cryptography.x509certificates.x509store.aspx) / [X509Store 類別](http://msdn.microsoft.com/zh-tw/library/system.security.cryptography.x509certificates.x509store.aspx) / [X509Store.Certificates 屬性](http://msdn.microsoft.com/zh-tw/library/system.security.cryptography.x509certificates.x509store.certificates.aspx)
-   [X509Certificate2Collection 類別](http://msdn.microsoft.com/zh-tw/library/system.security.cryptography.x509certificates.x509certificate2collection.aspx) / [X509Certificate2Collection.Find 方法](http://msdn.microsoft.com/zh-tw/library/system.security.cryptography.x509certificates.x509certificate2collection.find.aspx)
