28

私のソフトウェアを使用しているときに、ユーザーが最近奇妙なエラーを報告しました。DSA 署名を使用してライセンスを確認しています。ソフトウェアが署名を検証するために公開鍵をインポートすると、DSA プロバイダーのFromXmlStringメソッドは、 「 Key not valid for use in specified state.」という説明とともにCryptographicExceptionをスローします。

System.Security.Cryptography.Utils.CreateProvHandle から呼び出された _OpenCSP メソッドが NTE_BAD_KEY_STATE (0x8009000b) を返すように見えます。誰かがこのエラーを私に報告したのはこれが初めてであり、そのコードは何年も変更されていません.

これの考えられる原因は何ですか? マスクされたアクセス許可エラーですか? CAPI のインストールが壊れていますか? .net の信頼/権限設定によってブロックされていますか? キーストレージプロバイダーによって保存されたジャンク、または cryptoapi に予期しないものを返す KSP はありますか?

エラーコード/説明/などをグーグルで調べましたが、何が原因であるかについての本当の答えに出くわしませんでした...

失敗したコードの分離バージョンは次のとおりです: 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

更新:同じマシンで .net fx 2.0 を実行すると同じコードが正常に動作しますが、.net fx 4.0 では失敗します。

更新 2: DSA プロバイダーは、%APPDATA%\Microsoft\Crypto\DSS\[SID] に保存されているキーを探してから、既存のキーで初期化しているようです。このメカニズムと競合する可能性はありますか? その鍵ストレージがどのように動作するか、および文字列から公開鍵をロードするときにヒットするのはなぜですか?

4

1 に答える 1

85

あなたが説明した問題は私には非常に興味深いように思えますが、いくつかの追加情報といくつかの実験がなければ、理由が何であるかを明確に言うことは困難です. だから私は問題をどのように理解しているかを説明しようとします。

まず、.NET 暗号化クラスは、アンマネージ CryptoAPI を内部的に使用します。したがって、メソッド_OpenCSPは内部的に CryptAcquireContext関数を呼び出します。そのドキュメントでは、エラーNTE_BAD_KEY_STATE(0x8009000BL)について次のように読むことができます。

秘密鍵が暗号化されてから、ユーザーのパスワードが変更されました。

DSA プロバイダーによって使用されるユーザーの秘密鍵は、ディレクトリにファイルとして保存され、ここで%APPDATA%\Microsoft\Crypto\DSS\[SID]読むことができる比較的洗練されたアルゴリズムで暗号化されます。ディレクトリのファイルは、ユーザーのキーのキー コンテナーに対応していることを理解することが重要です。通常、ユーザーはファイル システム内のファイルに完全にアクセスできます。ファイルは、ユーザーのパスワードに依存するキーで暗号化されます。多くの標準的なケースでは、パスワードの変更後にファイルが再暗号化されますが、回復アルゴリズムは多くのことに依存します。ユーザー自身による変更ではなく (ドメイン管理者/アカウント オペレーターなどによって) パスワードがリセットされた場合、ディレクトリの古い内容%APPDATA%\Microsoft\Crypto\DSS\[SID]これ以上役に立ちません。たとえば、ユーザーが Active Directory ユーザー (ローカル ユーザー) ではなく、ローカル管理者がパスワードをリセットした場合、暗号化コンテナーの問題が発生します。

したがって、最初の提案は、Active Directory パスワードがリセットされたかどうかをユーザーに尋ねることです。次に、ディレクトリ%APPDATA%\Microsoft\Crypto\DSS\[SID]がユーザーのプロファイルに存在し、ユーザーがファイル システム内のディレクトリへのフル アクセス権を持っていることを確認する必要があります。ディレクトリからすべてのファイルを削除する必要があります (事前にファイルのバックアップ コピーを作成します)。ところで、ユーザーが中央保存プロファイル (サーバーに保存) を持っているかどうかを知ることは興味深いことです。中央プロファイルがある場合、説明した同じ問題がユーザーの他のコンピューターに存在し、他のユーザーが元のコンピューターに問題がないことを確認できます。

よくわからないもう 1 つの質問は、公開鍵のみを使用するため、ディレクトリのキー コンテナが使用される理由です。CryptoAPI では、 asパラメータとinを使用する必要があります。.NET がフラグを使用しているかどうかはわかりませんが、間接的な問題である可能性があります。%APPDATA%\Microsoft\Crypto\DSS\[SID]CryptAcquireContextNULLpszContainerCRYPT_VERIFYCONTEXTdwFlagsCRYPT_VERIFYCONTEXT

CspParametersパラメータを持つDSACryptoServiceProviderコンストラクタ作成できます。反対側のCspParametersには、.NET 4.0 で値CreateEphemeralKeyで拡張されたFlagsプロパティがあります。の説明は、関数のフラグの説明に非常に近いです。そのため、 CspProviderFlags.CreateEphemeralKeyを使用するか、(のパラメーターとして、デフォルトのキー コンテナーも意味します) を使用してみてください。CspProviderFlags.CreateEphemeralKeyCRYPT_VERIFYCONTEXTCryptAcquireContextCspProviderFlags.CreateEphemeralKeyCspProviderFlags.UseDefaultKeyContainerNULLpszContainerCryptAcquireContext

さらに、可能であれば、問題を再現できるコンピューターで問題のデバッグを試みることができます。デバッグには、有効にするか (ここここを参照)、またはここからダウンロードできる .NET ソースを使用できます。次に、プログラムで現在使用されている値に関するいくつかの質問に答え、.NET 3.5 と .NET 4.0 の値を比較できます。CspParameters

私が書いたことが問題の解決に役立たない場合は、質問に追加情報を追加してください:

  • 問題を再現できるコンピュータが搭載されているオペレーティング システムとサービス パックはどれですか?
  • 問題はユーザーに依存していますか? つまり、同じコンピューター上の他のユーザーに同じ問題がありますか?
  • 問題はドメイン (アクティブ ディレクトリ) ユーザーまたはローカル ユーザー アカウントに存在しますか? サーバーに保存されている問題のある中央ユーザー プロファイルを持つユーザーはいますか? 彼が持っている場合、ユーザーは他のコンピューターでも問題を再現できますか?
  • 公開鍵検証をどのような状況で使用するのか、もう少し環境を説明していただけますか? 特に、プログラムはユーザー セキュリティ コンテキストで実行されますか、それともなりすましを行いますか?

更新: 問題が最初に投稿されたフォーラムを読んだ後、問題の解決について悲観的になりました。問題を再現できるコンピューターに直接連絡することができず、問題を抱えている唯一のユーザーとのコミュニケーションがフォーラムへの投稿ごとにのみ行われる場合... それでも私は問題について考えていたので、自分で問題を再現してみてください。そして、私はこれに成功しました。そこで、ここで私の結果を注意深く説明します。が問題を再現する方法を説明し、フォーラム スレッドで説明されているとおりに見えるようにします。

次の手順を実行する必要があります。

1) コンピューターにローカル テスト アカウントを作成します。2) テスト アカウントでログインし、DSA プロバイダーのデフォルトのキー コンテナーを生成します。たとえば、次の単純な関数を呼び出す小さな .NET プログラムに関してこれを行うことができます。

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]この関数は、ディレクトリに作成され、生成されたキー ペアを含むファイルの名前を返します。3) テスト アカウントをログアウトし、ローカルの管理者権限を持つ別のアカウントでログインします。テスト アカウントのパスワードをリセットします。4) テスト アカウントでもう一度ログインし、.NET 4.0 用の Visual Studio 2010 でコンパイルした、投稿したテスト プログラムがエラーNTE_BAD_KEY_STATE(0x8009000b) を生成し、対応する例外がスローされることを確認します。5) .NET 4.0 の代わりに .NET 3.5 用にプログラムを再コンパイルすると (Visual Studio 2010 も使用できます)、テスト プログラムはエラーなしで実行されます。

そのため、すべての結果は、フォーラム スレッドに記載されているものとまったく同じになります。

DSA プロバイダのデフォルト キー コンテナを含むファイルを削除または名前変更すると、問題は解決します。前に説明したように、ユーザーのパスワードをリセットした後、デフォルトのキー コンテナーの内容を復号化できなくなります。そのため、ユーザーに固有のデフォルト コンテナーの名前がわかっている場合 (たとえば、Process Monitorのトレースから名前を確認できます)、他のユーザーおよび他のコンピューターから任意のキー コンテナー ファイルをコピーできます。 directory %APPDATA%\Microsoft\Crypto\DSS\[SID]、ファイルの名前を変更して、その名前がデフォルトのコンテナー名になるようにします。ユーザーのパスワードをリセットした場合とまったく同じ結果になります。

CspParametersのパラメーターとしてのさまざまな設定を使用していくつかの実験を行いました ( CspProviderFlags.CreateEphemeralKeyDSACryptoServiceProviderの使用に関する最初の提案を参照してください) が、成功しませんでした。その後、.NET 4.0 のソース コードをデバッグしましたが、コードが開かれていない関数の呼び出しは、コンストラクターのパラメーターとは独立したパラメーターで呼び出されると断言できます。したがって、.NET 4.0 の問題の回避策は、のパラメータとして の異なる設定を使用する方法では見つかりません。_OpenCSPDSACryptoServiceProviderCspParametersDSACryptoServiceProvider

したがって、コードを変更して、デフォルトのキープロバイダーが破損している状況でも機能するようにするには、現在、問題を解決する方法が 2 つしかありません。

  1. フラグCryptAcquireContext付きのアンマネージ関数を使用して、コードの全体または一部を実装します。CRYPT_VERIFYCONTEXT
  2. エラーNTE_BAD_KEY_STATE(0x8009000b)の問題を検出し%APPDATA%\Microsoft\Crypto\DSS\[SID]、破損した既定のキー コンテナーを含むファイルを削除または一時的に名前変更したコード部分を含めます。ファイルの名前を検出するには、 or/andパラメーターを指定CryptGetProvParamして関数を使用できると思います。PP_UNIQUE_CONTAINERPP_ENUMCONTAINERS

私の回答が長文で申し訳ありません。ここまで読んでくれたすべての人に感謝します。:-)

私の回答が、KristoferA さんが問題を解決し、開発するソフトウェアを改善するのに役立つことを願っています。

于 2010-11-28T01:34:36.577 に答える