Подтвердить что ты не робот

Импорт ключа DSA из строки xml не выполняется для одного пользователя. Права доступа? Сломанная установка? Bad KSP?

Пользователь недавно сообщил о странной ошибке при использовании моего программного обеспечения. Я использую подписи DSA для проверки лицензий. Когда программное обеспечение импортирует открытый ключ для проверки подписи, поставщик DSA FromXmlString выдает Криптографическое исключение с помощью описание "Недопустимый ключ для использования в указанном состоянии."

Похоже, что метод _OpenCSP, вызванный из System.Security.Cryptography.Utils.CreateProvHandle, возвращает NTE_BAD_KEY_STATE (0x8009000b). Это первый раз, когда кто-то сообщил об этой ошибке, и этот код не менялся годами.

Каковы вероятные причины этого? Ошибка маскированных разрешений? Сломанная установка CAPI? Заблокировано настройками доверия/разрешений .net? Хранение, хранящаяся у поставщика хранилища ключей, или KSP, возвращающий что-то неожиданное для криптоапи?

Я искал код ошибки/описание/etc, но не встречал никаких реальных ответов о том, что может вызвать это...

Изолированная версия кода, который терпит неудачу, находится здесь: 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], даже после инициализации существующим ключом. Может ли быть конфликт с этим механизмом? Кто-нибудь знает больше о том, как работает эта ключевая память, и почему она попадает при загрузке открытого ключа из строки?

4b9b3361

Ответ 1

Проблема, которую вы описываете, мне кажется очень интересной, но без дополнительной информации и некоторых экспериментов трудно окончательно сказать, в чем причина. Поэтому я пытаюсь описать, как я понимаю проблему.

Прежде всего, классы криптографии .NET используют внутренне неуправляемый CryptoAPI. Таким образом, метод _OpenCSP вызывает внутренне функцию CryptAcquireContext. В своей документации мы можем прочитать следующую информацию об ошибке NTE_BAD_KEY_STATE (0x8009000BL):

Пользовательский пароль изменился с тех пор секретные ключи были зашифрованы.

Пользователи частные ключи, используемые поставщиком DSA, сохраняются как файлы в каталоге %APPDATA%\Microsoft\Crypto\DSS\[SID] и будут зашифрованы относительно сложным алгоритмом, о котором вы можете прочитать здесь. Важно понимать, что файлы из каталога соответствуют контейнерам ключей пользовательских ключей. Обычно пользователь имеет полный доступ к файлам в файловой системе. Файлы будут зашифрованы ключом, зависящим от пароля пользователя. Во многих стандартных случаях файлы будут заново зашифрованы после смены пароля, но алгоритм восстановления, который зависит от многих факторов. Если пароль был reset вместо изменения самим пользователем (от администратора домена/учетной записи домена и т.д.), Старый список из каталога %APPDATA%\Microsoft\Crypto\DSS\[SID] может быть не более полезным. Например, если пользователь не является пользователем Active Directory (локальным пользователем) и локальным администратором reset его пароль, тогда проблема с криптоконтейнерами будет иметь место.

Итак, первое предложение - спросить у пользователя, был ли его пароль Active Directory reset. Затем вы должны убедиться, что каталог %APPDATA%\Microsoft\Crypto\DSS\[SID] существует в профиле пользователя, и пользователь имеет полный доступ к каталогу в файловой системе. Вы должны удалить все файлы из каталога (создавая ранее резервную копию файлов). Кстати, интересно узнать, имеет ли пользователь центральный сохраненный профиль (сохраненный на сервере). Если у него есть центральный профиль, можно проверить, что одна и та же проблема, которую вы описываете, существует на другом компьютере для пользователя и другого пользователя, не будет иметь проблем на его исходном компьютере.

Еще один вопрос, который для меня не совсем понятен, - это почему контейнер ключей из каталога %APPDATA%\Microsoft\Crypto\DSS\[SID] используется вообще, потому что вы используете только общедоступные ключи. В CryptoAPI следует использовать CryptAcquireContext с NULL как параметр pszContainer и CRYPT_VERIFYCONTEXT в dwFlags. Я не уверен, что .NET использует флаг CRYPT_VERIFYCONTEXT, и это может быть косвенным для вашей проблемы.

Вы можете создать DSACryptoServiceProvider с конструктором с параметром CspParameters. CspParameters с другой стороны имеет свойство Flags, которое расширено в .NET 4.0 со значением CreateEphemeralKey. Описание CspProviderFlags.CreateEphemeralKey очень близко к описанию флага CRYPT_VERIFYCONTEXT функции CryptAcquireContext. Таким образом, использование может использовать CspProviderFlags.CreateEphemeralKey или CspProviderFlags.CreateEphemeralKey вместе с CspProviderFlags.UseDefaultKeyContainer (NULL как pszContainer параметр CryptAcquireContext означает также контейнер ключей по умолчанию).

Кроме того, если это возможно, вы можете попробовать отладить проблему на компьютере, где проблема может быть воспроизведена. Для отладки вы можете использовать источники .NET, которые можно включить (см. здесь и здесь) или скачан из здесь. Затем вы можете ответить на некоторые вопросы о значениях CspParameters, которые в настоящее время используются в вашей программе, и сравнить значения для .NET 3.5 и .NET 4.0.

Если то, что я написал, не поможет решить проблему, пожалуйста, вы можете добавить свой вопрос с дополнительной информацией:

  • В какой операционной системе и в каком пакете обновления есть компьютер, на котором проблема может быть воспроизведена?
  • Является ли проблема зависимой от пользователя? Я имею в виду: есть ли другие пользователи на одном компьютере с той же проблемой?
  • Проблема связана с пользователем домена (активным каталогом) или с локальной учетной записью пользователя? У пользователя, у которого есть проблема с центральным профилем пользователя, который сохраняется на сервере? Если он имеет, то может ли проблема может быть воспроизведена и на других компьютерах пользователем?
  • Не могли бы вы описать немного больше среды, в которой вы используете проверку открытого ключа? Особенно, что программа работает в контексте безопасности пользователя или вы делаете какое-то олицетворение?

ОБНОВЛЕНО: После прочтения форума, на котором проблема была первоначально опубликована, я пессимистично отношусь к решению проблемы. Если у вас нет прямого контакта с компьютером, где проблема может быть воспроизведена, и связь с единственным пользователем, у которого есть проблема, выполняется только при размещении на форуме... Тем не менее, я думал о проблеме, и поэтому решил попытайтесь воспроизвести проблему самостоятельно. И у меня был успех в этом. Поэтому я подробно опишу здесь свои результаты. Я опишу, как может воспроизвести проблему, чтобы она выглядела точно так, как описано в в теме форума.

Вы должны сделать следующие шаги:

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) Вы выходите из тестовой учетной записи и авторизуетесь с другой учетной записью с правами локального администратора. Вы reset пароль тестовой учетной записи. 4) Вы снова входите в тестовую учетную запись и проверяете, что тестовая программа, которую вы опубликовали, скомпилированная в Visual Studio 2010 для .NET 4.0, выдает ошибку NTE_BAD_KEY_STATE (0x8009000b), и соответствующее исключение будет выбрано. 5) Если вы перекомпилируете программу для .NET 3.5 вместо .NET 4.0 (вы также можете использовать Visual Studio 2010), тестовая программа будет запущена без каких-либо ошибок.

Таким образом, все результаты будут точно такими, какие описаны в теме форума.

Если вы удалите или переименуете файл с контейнером ключей по умолчанию для поставщика DSA, проблема будет решена. Как я описал ранее после сброса пароля пользователя, содержимое контейнера ключей по умолчанию не может быть расшифровано. Поэтому, если вы знаете имя контейнера по умолчанию, уникального для пользователя (вы можете увидеть это имя, например, из следов Process Monitor), вы можете просто скопировать любой файл контейнера ключей от любого другого пользователя и любого другого компьютера в каталог %APPDATA%\Microsoft\Crypto\DSS\[SID], переименуйте файл, чтобы имя было именем контейнера по умолчанию и... у вас будут абсолютно такие же результаты, как при сбросе пользователей пароль.

Я провел несколько экспериментов с различными настройками CspParameters в качестве параметра DSACryptoServiceProvider (см. мои первые предложения об использовании CspProviderFlags.CreateEphemeralKey), но без каких-либо успехов. После этого я отлаживал исходный код .NET 4.0 и могу окончательно сказать, что вызов функции _OpenCSP, который не открывается, будет вызываться с параметрами independent из параметров конструктор DSACryptoServiceProvider. Таким образом, не удается найти обходной путь проблемы для .NET 4.0 с различными настройками CspParameters в качестве параметра DSACryptoServiceProvider.

Итак, если вы хотите изменить свой код, чтобы он также работал в ситуации с поврежденным поставщиком ключей по умолчанию, я вижу в настоящее время только 2 способа решения проблемы:

  • Внедрить целое или часть кода с помощью неуправляемой функции CryptAcquireContext с флагом CRYPT_VERIFYCONTEXT.
  • Определите проблему с ошибкой NTE_BAD_KEY_STATE (0x8009000b) и включите часть кода, которая удаляет или временно переименовывает файл из %APPDATA%\Microsoft\Crypto\DSS\[SID], который содержит поврежденный контейнер ключей по умолчанию. Чтобы определить имя файла, я думаю, вы можете попробовать использовать функцию CryptGetProvParam с параметрами PP_UNIQUE_CONTAINER или/и PP_ENUMCONTAINERS.

Прошу прощения за длинный текст моего ответа и спасибо всем, кто умеет читать его до этого места.: -)

Я надеюсь, что мой ответ поможет вам KristoferA решить проблему и улучшить программное обеспечение, которое вы разрабатываете.