2010-11-25 33 views
27

Un utilisateur a récemment signalé une erreur étrange lors de l'utilisation de mon logiciel. J'utilise des signatures DSA pour vérifier les licences. Lorsque le logiciel importe la clé publique pour vérifier une signature, la méthode du fournisseur de DSA FromXmlString jette un CryptographicException avec la description « Clé non valide pour une utilisation dans l'état spécifié. »L'importation d'une clé DSA à partir d'une chaîne XML échoue pour un utilisateur. Autorisations? Installation brisée? Mauvais KSP?

Il semblerait que la méthode _OpenCSP appelé à partir de System.Security.Cryptography.Utils.CreateProvHandle renvoie un NTE_BAD_KEY_STATE (0x8009000b). C'est la première fois que quelqu'un me signale cette erreur, et ce code n'a pas changé depuis des années.

Quelles sont les causes probables de cela? Une erreur d'autorisations masquées? Une installation CAPI cassée? Bloqué par les paramètres de confiance/autorisations .net? Junk stocké par un fournisseur de stockage de clés, ou un KSP retournant quelque chose d'inattendu à cryptoapi?

Je googlé le code d'erreur/description/etc, mais n'a pas rencontré de vraies réponses sur ce qui pourrait provoquer ce ...

Une version isolée du code qui n'est ici: 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(); 
    } 
} 

... et sur la machine de l'utilisateur concerné retourne:

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: Le même code fonctionne correctement sous .net fx 2.0 sur la même machine, mais échoue sous .net fx 4.0.

Mise à jour 2: Il semble que le fournisseur DSA recherche les clés stockées sous% APPDATA% \ Microsoft \ Crypto \ DSS \ [SID] même après l'initialisation avec une clé existante. Pourrait-il y avoir un conflit avec ce mécanisme? Quelqu'un en sait plus sur le fonctionnement de cette clé de stockage, et pourquoi est-il frappé lors du chargement d'une clé publique à partir d'une chaîne?

+0

Ajouté une prime ... – KristoferA

Répondre

83

Le problème, que vous décrivez, me semble très intéressant, mais sans quelques informations supplémentaires et quelques expériences il est difficile de dire définitivement quelle est la raison. Alors j'essaie de décrire comment je comprends le problème. En premier lieu, les classes de cryptographie .NET utilisent en interne le CryptoAPI non géré. Donc, la méthode _OpenCSP appel interne CryptAcquireContext fonction. Dans sa la documentation, nous pouvons lire la suite de l'erreur NTE_BAD_KEY_STATE (0x8009000BL):

Le mot de passe de l'utilisateur a changé depuis les clés privées ont été chiffrés.

utilisateurs privés clés utilisées par le fournisseur DSA sont enregistrées sous forme de fichiers dans le répertoire %APPDATA%\Microsoft\Crypto\DSS\[SID] et seront cryptés avec un algorithme relativement sophistiqué sur lequel vous pouvez lire here. Important de comprendre que les fichiers du répertoire correspondent aux conteneurs clés des clés de l'utilisateur. Généralement, l'utilisateur a un accès complet aux fichiers du système de fichiers. Les fichiers seront cryptés avec la clé qui dépend du mot de passe de l'utilisateur. Dans de nombreux cas standard, les fichiers seront rechiffrés après le changement de mot de passe, mais l'algorithme de récupération, qui dépend de beaucoup de choses.Si le mot de passe a été réinitialisé au lieu de changer par l'utilisateur lui-même (par un administrateur de domaine/opérateur de compte et ainsi de suite), l'ancien contenu du répertoire %APPDATA%\Microsoft\Crypto\DSS\[SID] pourrait ne pas être plus utile. Par exemple, si l'utilisateur n'est pas un utilisateur Active Directory (un utilisateur local) et que l'administrateur local a réinitialisé son mot de passe, alors le problème avec les conteneurs cryptographiques aura lieu.

Donc la première suggestion serait de demander à l'utilisateur si son mot de passe Active Directory a été réinitialisé. Ensuite, vous devez vérifier que le répertoire %APPDATA%\Microsoft\Crypto\DSS\[SID] existe dans le profil de l'utilisateur et que l'utilisateur a un accès complet au répertoire dans le système de fichiers. Le vous devez supprimer tous les fichiers du répertoire (en créant précédemment la copie de sauvegarde des fichiers). Par ailleurs, il est intéressant de savoir si l'utilisateur a un profil enregistré central (enregistré sur le serveur). S'il a un profil central, on peut vérifier que le même problème, que vous décrivez, existe sur l'autre ordinateur pour l'utilisateur et que l'autre utilisateur n'aura aucun problème sur son ordinateur d'origine.

Une question qui n'est pas tout à fait clair pour moi est pourquoi le conteneur clé du répertoire %APPDATA%\Microsoft\Crypto\DSS\[SID] sont utilisés du tout parce que vous utilisez uniquement clés publiques. Dans CryptoAPI, il faut utiliser CryptAcquireContext avec NULL comme paramètre pszContainer et CRYPT_VERIFYCONTEXT dans dwFlags. Je ne suis pas sûr que .NET utilise le drapeau CRYPT_VERIFYCONTEXT et il pourrait être indirect votre problème. Vous pouvez créer DSACryptoServiceProvider avec the constructor ayant le paramètre CspParameters. de l'autre côté a la propriété Flags qui est étendue dans .NET 4.0 avec la valeur CreateEphemeralKey. La description de CspProviderFlags.CreateEphemeralKey est très proche de la description du drapeau CRYPT_VERIFYCONTEXT de la fonction CryptAcquireContext. Donc, l'utilisation peut essayer d'utiliser CspProviderFlags.CreateEphemeralKey ou CspProviderFlags.CreateEphemeralKey avec CspProviderFlags.UseDefaultKeyContainer (NULL comme pszContainer paramètre de CryptAcquireContext signifie également le conteneur de clé par défaut). En outre, si cela est possible, vous pouvez essayer de déboguer le problème sur l'ordinateur où le problème peut être reproduit. Pour le débogage, vous pouvez utiliser les sources .NET qui peuvent être activées (voir here et here) ou téléchargé depuis here. Vous pouvez ensuite répondre à certaines questions sur les valeurs de CspParameters actuellement utilisées dans votre programme et comparer les valeurs de .NET 3.5 et .NET 4.0.

Si ce que j'ai écrit ne contribuera pas à résoudre le problème, s'il vous plaît pourriez-vous ajouter à votre question avec des informations supplémentaires:

  • Quel système d'exploitation et quel service pack a l'ordinateur où le problème peut être reproduit?
  • Le problème est-il lié à l'utilisateur? Je veux dire: a d'autres utilisateurs sur le même ordinateur le même problème?
  • Le problème existe-t-il avec l'utilisateur de domaine (répertoire actif) ou avec le compte d'utilisateur local? A l'utilisateur qui a le profil d'utilisateur central de problème qui sont sauvés sur le serveur? S'il a, que le problème peut être reproduit également sur les autres ordinateurs par l'utilisateur?
  • Pourriez-vous décrire un peu plus l'environnement dans lequel vous utilisez la vérification de clé publique? Surtout que le programme s'exécute dans le contexte de sécurité de l'utilisateur ou que vous faites une certaine usurpation d'identité?

MISE À JOUR: Après avoir lu du forum où le problème a été affiché, je deviens pessimiste sur la résolution du problème. Si vous n'avez aucun contact direct avec l'ordinateur où le problème pourrait être reproduit et la communication avec le seul utilisateur qui a le problème est faite seulement par affichage dans le forum ... Néanmoins, j'ai réfléchi au problème et j'ai donc décidé de essayez de reproduire le problème moi-même. Et j'ai eu du succès dans ça. Donc, je vais décrire ici mes résultats avec soin. Je vais décrire comment le peut reproduire le problème de sorte qu'il ressemble exactement à ce qui est décrit dans the forum thread.

Vous devez effectuer les étapes suivantes: Vous créez un compte de test local sur votre ordinateur

1). 2) Vous vous connectez avec le compte de test et générez le conteneur de clé par défaut pour le fournisseur DSA. Vous pouvez le faire par exemple en ce qui concerne le petit programme .NET qui appelle la fonction suivante simple:

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; 
} 

la fonction retourne le nom du fichier qui sera créé dans le répertoire %APPDATA%\Microsoft\Crypto\DSS\[SID] et qui contiendra la clé générée paire. 3) Vous vous déconnectez du compte de test et vous vous connectez avec un autre compte disposant de droits d'administration locaux. Vous réinitialisez le mot de passe du compte de test. 4) Vous vous connectez une fois de plus avec le compte de test et vérifiez que le programme de test que vous avez compilé dans Visual Studio 2010 pour .NET 4.0 génère l'erreur NTE_BAD_KEY_STATE (0x8009000b) et l'exception correspondante sera levée. 5) Si vous recompilez le programme pour .NET 3.5 au lieu de .NET 4.0 (vous pouvez également utiliser Visual Studio 2010), le programme de test sera exécuté sans erreur.

Donc, tous les résultats seront exactement comme il est décrit dans the forum thread.

Si vous supprimez ou renommer le fichier avec le conteneur de clé par défaut pour le fournisseur DSA, le problème sera résolu. Comme je l'ai décrit précédemment après la réinitialisation du mot de passe des utilisateurs, le contenu du conteneur de clé par défaut ne pourra pas être déchiffré. Donc, si vous connaissez le nom du conteneur par défaut qui est unique pour l'utilisateur (vous pouvez voir le nom par exemple à partir des traces du Process Monitor) vous pouvez simplement copier n'importe quel fichier conteneur de clé d'un autre utilisateur et n'importe quel autre ordinateur dans le répertoire %APPDATA%\Microsoft\Crypto\DSS\[SID], renommer le fichier de sorte que le nom sera le nom du conteneur par défaut et ... vous aurez absolument les mêmes résultats comme avec la réinitialisation du mot de passe des utilisateurs.

j'ai fait quelques expériences avec différents paramètres du CspParameters comme paramètre de DSACryptoServiceProvider (voir mes premières suggestions au sujet de l'utilisation de CspProviderFlags.CreateEphemeralKey), mais sans aucun succès. Après que j'ai débogué le code source de .NET 4.0 et peut définitivement dire que l'appel de _OpenCSP fonction, ce code n'est pas ouvert, sera appelé avec les paramètres qui sont indépendant à partir des paramètres du constructeur DSACryptoServiceProvider. Donc, on ne peut pas trouver une solution de contournement du problème pour .NET 4.0 de la manière avec les différents paramètres du CspParameters comme paramètre de DSACryptoServiceProvider.

Donc, si vous voulez modifier votre code afin qu'il devrait fonctionner également dans la situation avec le fournisseur de clé par défaut corrompu que je vois actuellement que deux façons de résoudre le problème:

  1. Mettre en oeuvre tout ou partie de code utilisant la fonction CryptAcquireContext non gérée avec le drapeau CRYPT_VERIFYCONTEXT.
  2. Détectez le problème avec l'erreur NTE_BAD_KEY_STATE (0x8009000b) et incluez la partie de code supprimant ou renommant temporairement le fichier de %APPDATA%\Microsoft\Crypto\DSS\[SID] qui contient le conteneur de clé par défaut endommagé. Pour détecter le nom du fichier, je pense que vous pouvez essayer d'utiliser la fonction CryptGetProvParam avec les paramètres PP_UNIQUE_CONTAINER ou/et PP_ENUMCONTAINERS.

Je suis désolé pour le long texte de ma réponse et merci à tous ceux qui sont capables de le lire jusqu'à cet endroit. :-)

J'espère que ma réponse vous aidera KristoferA pour résoudre le problème et améliorer le logiciel que vous développez.

+0

Re. le public-privé: oui, ça a aussi fait lever mes sourcils. En utilisant l'exemple de code dans ma question et le ProcMon de Sysinternals, vous pouvez voir que même en important une clé publique, CryptoAPI s'éteint et frappe le magasin de clés. Cela se passe aussi sur mon propre système, mais ne provoque aucune erreur ici. Juste intéressant de voir ... – KristoferA

+1

J'ai demandé à l'utilisateur pour plus de détails et d'utiliser procmon sur son système pour voir si cela peut révéler des erreurs sous-jacentes, par exemple. les erreurs d'autorisation, etc., mais n'ont pas encore reçu de réponse de lui. Le fil du forum où le problème a été initialement rapporté est ici: http://forum.huagati.com/topic167-standardize-linq-to-sql-class-and-names-failed.aspx – KristoferA

+2

@KristoferA - Huagati.com: I lire le fil du forum et après quelques expériences je pourrais reproduire/simuler le problème décrit. Voir plus dans la partie "mise à jour" de ma réponse. – Oleg