Импорт ключа DSA из строки xml не выполняется для одного пользователя. Права доступа? Сломанная установка? Плохой ксп? - PullRequest
28 голосов
/ 25 ноября 2010

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

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

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

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

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

1 Ответ

85 голосов
/ 28 ноября 2010

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

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

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

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

Таким образом, первым предложением будет спроситьПользователь, был ли сброшен его пароль Active Directory.Затем вы должны убедиться, что каталог %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) Вы выходите из тестовой учетной записи и входите в систему с другой учетной записью, которая имеет права локального администратора.Вы сбрасываете пароль тестовой учетной записи.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, код которой не открывается, будет вызываться с параметрами, независимыми от параметровDSACryptoServiceProvider конструктор.Таким образом, невозможно найти обходной путь проблемы для .NET 4.0 в пути с другими настройками CspParameters в качестве параметра DSACryptoServiceProvider.

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

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

Извините за длинный текст моего ответаи спасибо всем, кто может прочитать его до этого места.: -)

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

...