2010-11-25 34 views
27

用戶最近在使用我的軟件時報告了一個奇怪的錯誤。我使用DSA簽名來驗證許可證。當軟件導入的公鑰來驗證簽名,將DSA提供商的FromXmlString方法拋出一個CryptographicException記載着「鍵不適於在指定狀態下使用。從一個用戶導入xml字符串的DSA密鑰失敗。權限?破碎的安裝?壞的KSP?

這樣看來,所謂的_OpenCSP方法從System.Security.Cryptography.Utils.CreateProvHandle返回一個NTE_BAD_KEY_STATE(0x8009000b)。這是任何人第一次向我報告這個錯誤,並且該代碼多年來沒有改變。

這可能是什麼原因造成的?蒙面的權限錯誤?破損的CAPI安裝?被.net信任/權限設置阻止?由密鑰存儲提供商存儲的垃圾或KSP將意外事件返回給cryptoapi?

我用Google搜索錯誤代碼/說明/等,但在任何真正的答案是什麼可能會導致這樣沒來......

失敗的代碼分離的版本是在這裏: http://forum.huagati.com/getattachment.ashx?fileid=78

using System; 
using System.Security.Cryptography; 
using System.Reflection; 

public class Test 
{ 
    public static void Main() 
    { 
    try 
    { 
     string key = "<DSAKeyValue><P>wrjxUnfKvH/1s5cbZ48vuhTjflRT5PjOFnr9GeUPZSIoZhYATYtME4JRKrXBtSkyioRNtE1xgghbGAyvAJ5jOWw88fLBF+P1ilsZyq72G1YcbB+co8ImQhAbWKmdCicO9/66Th2MB+7kms/oY3NaCzKEuR7J3b23dGrFpp4ccMM=</P><Q>xmxoSErIJCth91A3dSMjC6yQCu8=</Q><G>bwOLeEaoJHwSiC3i3qk9symlG/9kfzcgrkhRSWHqWhyPAfzqdV1KxJboMpeRoMoFr2+RqqKHgcdbzOypmTeN4QI/qh4nSsl5iEfVerarBOrFuRdOVcJO0d8WE233XQznd1K66nXa5L8d9SNZrM6umZ1YuBjhVsTFdPlIXKfGYhk=</G><Y>wZnEEdMUsF3U3NBQ8ebWHPOp37QRfiBn+7h5runN3YDee1e9bC7JbJf+Uq0eQmU8zDs+avEgD68NpxTKEHGr4nQ3rW6qqacj5SDbwO7nI6eN3wWrVhvrWcQm0tUO93m64HsEJREohfoL+LjqgrqIjZVT4D1KXE+k/iAb6WKAsIA=</Y><J>+zmcCCNm2kn1EXH9T45UcownEe7JH+gl3Lw2lhVzXuX/dYp5sGCA2lK119iQ+m3ogjOuwABATCVFLo6J66DsSlMd0I8WSD5WKPvypQ7QjY0Iv71J2N0FW0ZXpMlk/CE8zq4Z7arM1N564mNe</J><Seed>QDrZrUFowquY5Uay8YtUFOXnv28=</Seed><PgenCounter>Gg==</PgenCounter></DSAKeyValue>"; 

     DSACryptoServiceProvider csp2 = new DSACryptoServiceProvider(); 
     csp2.FromXmlString(key); 

     Console.WriteLine("Success!"); 
    } 
    catch (Exception ex) 
    { 
     int hResult = 0; 
     try 
     { 
      PropertyInfo pi = typeof(Exception).GetProperty("HResult", BindingFlags.NonPublic | BindingFlags.Instance); 
      hResult = (int)pi.GetValue(ex, null); 
     } 
     catch (Exception ex2) 
     { 
      Console.WriteLine("HResult lookup failed: " + ex2.ToString()); 
     } 
     Console.WriteLine("Initializing CSP failed: " + ex.ToString() + "\r\nHResult: " + hResult.ToString("x")); 
    } 
    Console.WriteLine("\r\nPress Enter to continue"); 
    Console.ReadLine(); 
    } 
} 

...和受影響用戶的機器上,它返回:

Initializing CSP failed: System.Security.Cryptography.CryptographicException: Ke 
y not valid for use in specified state. 

at System.Security.Cryptography.Utils.CreateProvHandle(CspParameters paramete 
rs, Boolean randomKeyContainer) 
at System.Security.Cryptography.Utils.get_StaticDssProvHandle() 
at System.Security.Cryptography.DSACryptoServiceProvider.ImportParameters(DSA 
Parameters parameters) 
at System.Security.Cryptography.DSA.FromXmlString(String xmlString) 
at Test.Main() 
HResult: 8009000b 

Updat e:在同一臺機器上運行.net fx 2.0時,相同的代碼工作正常,但在.net fx 4.0下失敗。

更新2:即使使用現有密鑰進行初始化,似乎DSA提供程序也會查找存儲在%APPDATA%\ Microsoft \ Crypto \ DSS \ [SID]下的密鑰。這個機制會不會有衝突?任何人都知道更多關於這個關鍵存儲器的操作方式,以及爲什麼從字符串中加載公鑰時碰到它?

+0

增加了一個賞金... – KristoferA 2010-11-27 06:00:16

回答

83

你所描述的問題看起來非常有趣,但沒有一些額外的信息和一些實驗,很難說明是什麼原因。所以我試圖描述我如何理解這個問題。

首先.NET加密類在內部使用非託管CryptoAPI。所以方法_OpenCSP內部調用CryptAcquireContext函數。在它的文件我們可以看到以下有關錯誤NTE_BAD_KEY_STATE(0x8009000BL):因爲 私鑰加密了

用戶密碼已更改。通過DSA提供者使用

用戶私人按鍵都在目錄%APPDATA%\Microsoft\Crypto\DSS\[SID]保存爲文件,將相對複雜的算法有關,你可以閱讀here進行加密。重要的是要明白,目錄中的文件對應於用戶密鑰的密鑰容器。通常,用戶可以完全訪問文件系統中的文件。這些文件將使用取決於用戶密碼的密鑰進行加密。在許多標準情況下,密碼更改後文件將被重新加密,但恢復算法依賴於許多事情。如果密碼被重置,而不是由用戶自己更改(來自域管理員/帳戶操作員等),則目錄%APPDATA%\Microsoft\Crypto\DSS\[SID]的舊包含可能沒有多大用處。例如,如果用戶不是Active Directory用戶(本地用戶),並且本地管理員重置密碼,那麼加密容器的問題將會發生。

所以第一個建議是詢問用戶他的Active Directory密碼是否被重置。接下來,您應驗證目錄%APPDATA%\Microsoft\Crypto\DSS\[SID]存在於用戶的配置文件中,並且用戶可以完全訪問文件系統中的目錄。您應該從目錄中刪除所有文件(先前創建文件的備份副本)。順便說一下,知道用戶是否具有中央保存的配置文件(保存在服務器上)很有趣。如果它具有中央配置文件,則可以驗證您描述的同一問題存在於另一臺計算機上,以供其他用戶使用,而其他用戶在其原始計算機上沒有問題。

還有一個問題是不適合我不太清楚是爲什麼的目錄%APPDATA%\Microsoft\Crypto\DSS\[SID]密鑰容器在所有因爲你只使用公鑰使用。在CryptoAPI中,應使用CryptAcquireContextNULL作爲pszContainer參數,並使用CRYPT_VERIFYCONTEXTdwFlags中。我不確定.NET使用CRYPT_VERIFYCONTEXT標誌,它可能是間接的問題。

您可以創建DSACryptoServiceProviderthe constructor具有CspParameters參數。另一方面的CspParameters具有Flags屬性,它在.NET 4.0中的值爲CreateEphemeralKeyCspProviderFlags.CreateEphemeralKey的描述非常接近CryptAcquireContext函數的CRYPT_VERIFYCONTEXT標誌的描述。因此,使用可以嘗試使用CspProviderFlags.CreateEphemeralKeyCspProviderFlags.CreateEphemeralKey以及CspProviderFlags.UseDefaultKeyContainerNULL作爲pszContainer參數CryptAcquireContext也意味着默認密鑰容器)。

此外,如果有可能,您可以嘗試在可以複製問題的計算機上調試問題。對於調試,您可以使用可以啓用的.NET源(請參閱herehere)或here下載。然後,您可以回答一些關於目前在您的程序中使用的CspParameters的值的問題,並比較.NET 3.5和.NET 4.0的值。

如果我寫不會幫助解決什麼問題,請您可以添加你的問題有額外的信息:

  • 哪個操作系統和服務包具有問題出在哪裏可以重現的電腦嗎?
  • 問題用戶是否依賴?我的意思是:在同一臺計算機上有其他用戶有同樣的問題?
  • 問題是否存在域(活動目錄)用戶或與本地用戶帳戶?是否有問題中央用戶配置文件保存在服務器上的用戶?如果他有問題,用戶也可以在其他計算機上再現該問題?
  • 您能否詳細介紹一下您使用公鑰驗證的環境?特別是程序運行在用戶安全上下文中,或者你做了一些模擬?

修訂:讀取其中的問題最初發布在論壇後,我變得悲觀問題的解決。如果您沒有與可以複製問題的計算機直接聯繫,並且只有在論壇中發帖才能與唯一有問題的用戶進行溝通......但是我一直在考慮這個問題,因此我決定嘗試自己再現問題。我在這方面取得了成功。所以我會在這裏仔細描述我的結果。我將描述如何重現問題,使其看起來完全像在the forum thread中描述的那樣。

你應該做以下步驟:

1)您在計算機上創建本地測試帳戶。 2)您使用測試帳戶登錄併爲DSA提供程序生成默認密鑰容器。

static string GenerateDsaKeyInDefaultContainer() 
{ 
    const int PROV_DSS_DH = 13; 
    CspParameters cspParam = new CspParameters(PROV_DSS_DH); 
    cspParam.KeyContainerName = null; 
    cspParam.KeyNumber = (int)KeyNumber.Signature; 
    cspParam.Flags = CspProviderFlags.UseDefaultKeyContainer; 
    DSACryptoServiceProvider csp = new DSACryptoServiceProvider(cspParam); 
    return csp.CspKeyContainerInfo.UniqueKeyContainerName; 
} 

該函數返回將在目錄%APPDATA%\Microsoft\Crypto\DSS\[SID]創建和文件的名稱將包含生成的密鑰:您可以相對於它調用下面的簡單功能的小程序。NET爲例子做對。 3)您註銷測試帳戶並使用另一個具有本地管理權限的帳戶登錄。您重置測試帳戶的密碼。 4)您再次登錄測試帳戶,並驗證您發佈的測試程序是否在Visual Studio 2010 for .NET 4.0中編譯時產生錯誤NTE_BAD_KEY_STATE(0x8009000b),並且會拋出相應的異常。 5)如果您重新編譯用於.NET 3.5而不是.NET 4.0的程序(也可以使用Visual Studio 2010),測試程序將無任何錯誤地運行。

所以所有結果都會與the forum thread中描述的完全相同。

如果使用DSA提供程序的默認密鑰容器刪除或重命名文件,問題將得到解決。就像我之前在重置用戶密碼之後描述的那樣,默認密鑰容器的包含將不能被解密。因此,如果您知道用戶唯一的默認容器名稱(例如您可以從Process Monitor的跟蹤中看到名稱),則可以將任何其他用戶和任何其他計算機上的密鑰容器文件複製到該目錄%APPDATA%\Microsoft\Crypto\DSS\[SID],重命名該文件,以便該名稱將成爲默認的容器名稱,並且...您將擁有與重置用戶密碼完全相同的結果。

我用CspParameters的不同設置做了一些實驗,作爲DSACryptoServiceProvider的參數(請參閱我對於CspProviderFlags.CreateEphemeralKey的使用的第一個建議),但沒有任何成功。之後,我調試了.NET 4.0的源代碼,並可以明確地說,將調用_OpenCSP函數(不打開代碼)的調用參數是獨立DSACryptoServiceProvider構造函數的參數。因此,用CspParameters的不同設置作爲DSACryptoServiceProvider的參數,您無法找到.NET 4.0問題的解決方法。

所以,如果你想修改你的代碼,以便它應與損壞的默認密鑰提供商我看到目前只有2種方式來解決問題的情況也工作:

  1. 的全部或部分實施的代碼使用非託管CryptAcquireContext功能與CRYPT_VERIFYCONTEXT標誌。
  2. 檢測與誤差NTE_BAD_KEY_STATE(0x8009000b)的問題,並且包括刪除或臨時從%APPDATA%\Microsoft\Crypto\DSS\[SID]它含有已損壞的默認密鑰容器重命名的文件的代碼部分。爲了檢測我想你可以嘗試使用CryptGetProvParam功能與PP_UNIQUE_CONTAINER或/和PP_ENUMCONTAINERS參數的文件的名稱。

我很抱歉我的答案的長文本,並感謝所有能夠閱讀到此地的人。 :-)

我希望我的回答確實有助於你KristoferA解決問題,提高您開發的軟件。

+0

回覆。公共 - 私人:是的,這也提高了我的眉毛。在我的問題和Sysinternals的ProcMon中使用示例代碼,您可以看到即使只是導入一個公鑰,CryptoAPI也會關閉並擊中關鍵存儲區。這也發生在我自己的系統上,但這裏不會導致任何錯誤。只是有趣的,看看... – KristoferA 2010-11-28 03:03:53