我最近因為需要在 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 這份 KB,也成功的解決無法載入憑證檔的問題,詳細問題發生原因文件裡都有說明我就不再贅述,以下分享解決問題的過程,一共三大步驟:
第一步:將PFX或P12憑證檔匯入本機電腦的憑證儲存區
將憑證匯入到本機電腦的憑證儲存區(Local Computer certificate store)是為了要讓憑證可以在本機電腦共用,這樣才可以讓 ASP.NET 正確載入憑證。
1. 在 [開始] -> [執行] --> 輸入 mmc 執行「主控台」程式
2. 按下 Ctrl + M 準備新增嵌入式管理單元
3. 接著新增 [憑證] --> [電腦帳戶] --> [本機電腦] --> [完成]
4. 然後準備匯入你無法載入的 PFX 憑證檔。[選取檔案] --> [輸入密碼] --> [下一步] --> [完成]
5. 匯入成功後,你就會在憑證清單的地方看到你剛剛匯入的憑證,其中的 [發給] 欄位所列的名稱你必須先記下來,後面的步驟會用到。
第二步:授權憑證的使用權利給 ASP.NET 執行時的那個身份識別
ASP.NET 執行時的身份識別會定義在 IIS 的應用程式集區裡,預設值是「網路服務」,也就是 "Network Service" 帳戶。
1. 先下載安裝 WinHttpCertCfg.exe 工具 ( 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),也就是上面那張圖的 [發給] 中的名稱。
延伸閱讀
相關連結