Exporter (0) Imprimer
Développer tout

Bulletins de sécurité : Les noms forts et la sécurité dans .NET Framework

Keith Brown
DevelopMentor

S'applique à :
   Microsoft® .NET Framework

Résumé : Les noms forts sont nécessaires pour identifier de façon unique un assembly, afin de pouvoir le placer dans le Global Assembly Cache, ainsi que pour utiliser le système de gestion des versions dans le CLR (Common Language Runtime) de Microsoft .NET Framework. Lisez cet article pour en savoir plus sur les noms forts et leur utilisation.

Sommaire

Des GUID aux clés publiques
RSA et les signatures numériques
Le CLR et les clés publiques
Noms forts et vérification
Les noms forts et la stratégie de sécurité .NET
Clés publiques et versions
Utilisation de la signature différée pour réduire les risques
Protection de votre équipe de développeurs
Conclusion

Des GUID aux clés publiques

Pour comprendre le principe des noms forts, il peut être utile d'examiner l'ancien schéma de nommage des composants sur la plate-forme Microsoft® Windows®, à savoir l'identificateur global unique, ou GUID (Globally Unique Identifier). Un GUID est un entier unique de 128 bits (16 octets) utilisé pour nommer des éléments dans le monde COM. Quiconque a parcouru plus ou moins le registre sait qu'il existe une multitude de GUID. Les programmeurs COM savent aussi que le nombre des GUID est dû au fait qu'ils étaient utilisés pour nommer de très petits éléments. Chaque classe COM, interface COM, application, bibliothèque de types et énumération a besoin de son propre GUID. Il y a réellement surabondance de GUID !

Avant Windows 2000, les GUID étaient générés sur la base de l'identificateur universel unique, ou UUID (Universally Unique Identifier), défini dans le cadre de la norme RPC DCE. En bref, l'unicité de l'UUID vient de la date et de l'heure du jour, plus de l'adresse unique IEEE 802 de 48 bits spécifique de votre carte d'interface réseau (NIC).

À partir de Windows 2000, les GUID ne sont plus générés sur la base de cet algorithme. Ils sont désormais des entiers aléatoires de 16 octets formés par le générateur de nombres aléatoires fourni par CryptoAPI. Pourquoi ce changement ? Probablement en raison d'un problème de confidentialité apparu au début de 1999. En effet, Microsoft® Office utilisait le GUID comme identificateur unique dans des fichiers de données. Malheureusement, cette utilisation avait pour inconvénient de permettre qu'un document soit lié à nouveau à son créateur via l'adresse NIC utilisée dans le GUID. L'association de GUID à des fichiers de données s'avérait désastreuse pour la sécurité : elle violait l'anonymat.

Compte tenu de tous les problèmes liés au nommage dans le modèle COM, l'équipe de développement du CLR (Common Language Runtime) a cherché à mettre au point un meilleur moyen d'identifier les composants de façon unique. Une des décisions stratégiques a été d'utiliser un schéma de nommage hiérarchique. Au lieu que chaque type individuel possède son propre identificateur unique, comme c'était le cas dans le modèle COM avec les GUID, les types CLR seraient identifiés sur la base de leur nom de type complet, y compris l'espace de nom, plus le nom de l'assembly dans lequel le type était empaqueté. Ceci permettait d'utiliser des noms de type simples pour les classes, les interfaces, etc. Étant donné que, pour le chargeur, le nom de l'assembly fait partie du nom de chaque type, il suffit de donner à chaque assembly un nom unique dans l'espace et le temps, bien que, du point de vue pratique, les espaces de nom soient importants pour résoudre les conflits de nommage à la compilation. Avec un schéma hiérarchique de nommage, le problème de la prolifération des GUID était résolu une fois pour toutes.

L'équipe CLR n'avait plus qu'à définir un algorithme pour générer un nom d'assembly unique. Ainsi, une partie du nom pouvait être constituée d'un nombre aléatoire élevé semblable à un GUID, peut-être même de plus de 16 octets, afin de limiter les risques de collision. Cette solution évitait que quelqu'un choisisse accidentellement un identificateur dont vous vous serviez déjà pour nommer vos assemblys. En revanche, vous n'étiez pas protégé contre un utilisateur malveillant cherchant à faire un cheval de Troie à partir d'un de vos assemblys. L'équipe CLR a alors pris une décision intéressante. Au lieu de simplement utiliser un grand nombre aléatoire, elle a décidé d'utiliser une clé publique RSA de 1024 bits, qui est un nombre de 128 octets formé à partir de deux très grands nombres premiers aléatoires multipliés ensemble.

Pour bien comprendre les noms forts, il est important de connaître le rôle de la cryptographie ; aussi allons-nous en parler ici.

RSA et les signatures numériques

Pour une présentation détaillée de ce sujet, reportez-vous à l'ouvrage Practical Cryptography de Ferguson et Schneier (Wiley 2003). Brièvement, l'idée à la base de RSA est que les clés sont générées dans des paires : une clé publique et une clé privée. La clé privée doit rester secrète, vous ne devez la divulguer à personne. La clé publique en revanche peut être connue de tout le monde. Il est admis qu'il est impossible de calculer la clé privée à partir de la clé publique. Les données cryptées avec la clé publique ne peuvent être décryptées qu'avec la clé privée et, selon le même principe, les données cryptées avec la clé privée ne peuvent être décryptées qu'avec la clé publique.

Les clés RSA sont utilisables pour assurer l'intégrité des données via une signature numérique. Pour signer certaines données, vous devez d'abord les hacher à l'aide d'un algorithme cryptographique de hachage, puis crypter la valeur de hachage ainsi obtenue avec votre clé privée. La signature n'est en fait que cette valeur de hachage cryptée. Si vous publiez les données et la signature, quiconque connaît votre clé publique peut valider la signature en hachant les données puis en comparant la valeur de hachage obtenue avec celle de votre signature, qu'il décrypte à l'aide de votre clé publique. L'idée est que les hachages ne correspondront pas si les deux conditions suivantes ne sont pas remplies : les données reçues doivent être les mêmes que celles signées à l'origine et le signataire connaît la clé privée. Lorsque cette technique est appliquée à un assembly, il est impossible pour un pirate de remplacer votre assembly par sa version de type cheval de Troie car il ne peut pas contrefaire votre signature. Ceci suppose, naturellement, qu'il ne connaît pas votre clé privée. La protection des clés privées est donc essentielle et leur communication est l'une des raisons qui justifient cet article.

En choisissant d'utiliser une clé publique RSA dans le nom d'un assembly, l'équipe de développement du CLR a fait d'une pierre deux coups. Premièrement, le fait que les clés publiques RSA générées de façon aléatoire éliminent les risques de collision assure la sécurité face à des conflits de nommage accidentels : une clé publique de 1024 bits est huit fois plus longue qu'un GUID. Deuxièmement, la clé privée correspondante est utilisable pour signer un assembly, ce qui assure la sécurité contre un pirate qui tenterait de remplacer votre assembly par son propre code. Cependant, comme toujours en matière de sécurité, il y a un prix à payer : nous avons à présent des secrets à préserver. Si votre clé privée est compromise, des violations de sécurité et des ruptures catastrophiques de compatibilité peuvent se produire.

Le CLR et les clés publiques

Un assembly doté d'un nom fort est un assembly auquel une clé publique a été attribuée. Cette attribution est réalisée par un compilateur. Par exemple, si vous générez une paire de clés pour vous-même, vous pouvez affecter votre clé publique à des assemblys que vous créez en indiquant simplement au compilateur où chercher votre fichier de clé :

using System.Reflection;
[assembly: AssemblyKeyFile(@"c:\temp\mykeyfile")]

class Foo {...}

La plupart des projets générés par assistant dans Microsoft Visual Studio® .NET placent cet attribut dans un fichier appelé AssemblyInfo, mais vous pouvez le mettre dans un quelconque fichier source de votre choix. Lorsque le compilateur voit cet attribut, il copie la clé publique complète dans les métadonnées de l'assembly et utilise la clé privée pour composer une signature numérique. Au cours de ce processus, les fichiers de l'assembly sont hachés, les valeurs de hachage sont incorporées dans le manifeste relatif à l'assembly, ce manifeste est haché et, pour finir, la valeur de hachage finale est cryptée à l'aide de la clé privée correspondante, puis dissimulée parmi les blocs de métadonnées de l'assembly.

(En utilisant cette approche très simple, le compilateur doit non seulement disposer de la clé publique, mais aussi de la clé privée, à partir du fichier c:\temp\mykeyfile. Nous verrons plus loin un moyen plus sûr d'aborder la question de la signature du code.)

Si vous examinez le manifeste d'un assembly avec ILDASM, vous pouvez voir sa clé publique en clair, comme le montre la figure 1.

strongnames01.gif

Figure 1. Une clé publique attribuée à un assembly

En revanche, vous ne verrez pas la signature ou les valeurs de hachage intermédiaires. Ceci peut créer une certaine confusion chez les personnes qui découvrent le principe des noms forts. ILDASM ne montre pas ces éléments car c'est un désassembleur, il est censé produire un IL (langage intermédiaire) qui peut être compilé en un assembly. N'oubliez pas non plus que la signature et les valeurs de hachage sont produites par le compilateur (qui est dans ce cas l'assembleur ILASM). Pour savoir si une clé publique a été attribuée à un assembly, utilisez l'outil de nom fort SN.EXE :


sn -Tp foo.dll


Celui-ci imprime la clé publique de l 'assembly ou vous indique que l'assembly n'a pas un nom fort, ce qui veut dire qu'il n'a pas de clé publique. Mais le fait qu'un assembly ait une clé publique ne signifie pas nécessairement qu'il a une signature correspondante ou que la signature est valide. Pour vérifier l'existence et la validité d'une signature, utilisez la commande suivante :

sn -vf foo.dll

Sous l'effet de cette commande, SN.EXE calcule son propre hachage pour chaque fichier de l'assembly afin de vérifier que les fichiers binaires de ce dernier n'ont pas changé depuis qu'ils ont été signés. Il calcule ensuite son propre hachage du manifeste de l'assembly, décrypte la signature incorporée dans l'assembly (celle que ILDASM ne vous montre pas) et compare la valeur de hachage qu'il a calculée avec la signature décryptée. Si le hachage correspond, il signale que l'assembly est valide. Sinon, il renvoie une erreur. Par exemple, il y a une erreur si aucune signature n'a encore été affectée à l'assembly, ce qui est le cas avec les assemblys à signature différée (comme nous le verrons plus loin).

Lorsque vous utilisez les outils fournis avec .NET Framework pour installer un nouvel assembly dans le Global Assembly Cache (GACUTIL.EXE ou la visionneuse de cache Fusion), ceux-ci procèdent à une vérification de signature qui équivaut à la commande suivante :

sn -v foo.dll

La même chose se produit chaque fois que le CLR charge un assembly doté d'un nom fort qui ne réside pas dans le GAC. S'il existe une clé publique, il vérifie la signature. La légère différence entre les commandes sn –vf et sn -v est que la seconde ignore la vérification de signature pour les clés publiques que l'administrateur a enregistrées comme étant fiables (voir plus loin). Pour résumer, le CLR vérifie les signatures de l'assembly au moment de son chargement ou de son installation dans le GAC.

Notez que le GAC est considéré comme étant un référentiel fiable : la seule protection d'un assembly contre une éventuelle modification une fois qu'il est installé dans le GAC est offerte par l'ACL (liste de contrôle d'accès) du système de fichiers qui s'applique à tout ce qui réside dans le cache GAC. Il s'agit en gros de la même ACL qui protège le CLR et les autres fichiers binaires du système d'exploitation. Si un pirate acquiert le contrôle d'un ordinateur en tant qu'administrateur, il peut remplacer des assemblys du GAC par des versions de type cheval de Troie ; le CLR ne sera pas très efficace puisqu'il ne revérifie pas les signatures lorsque les assemblys sont chargés à partir du GAC. Mais si le pirate a un accès suffisant au système de fichiers pour modifier ou remplacer des assemblys du GAC, il peut en faire de même avec les fichiers binaires du CLR (MSCORWKS.DLL et associés) ou du système d'exploitation lui-même. Pour sa part, le CLR ne cherche pas à revérifier les signatures des assemblys chargés à partir du GAC, afin de réduire les temps de chargement.

Noms forts et vérification

Le solveur d'assembly CLR fait référence aux assemblys de l'une des deux manières suivantes : faible ou forte. La méthode faible considère uniquement le nom court de l'assembly, qui correspond juste au nom de fichier moins l'extension. Par exemple, le nom court d'assembly pour FOO.DLL est FOO. Aucune vérification de version n'a lieu au moment du chargement. Un nom fort en revanche se compose du nom court plus trois autres parties : un numéro de version, un code de culture et une clé publique. Si vous affectez une clé publique à votre assembly, il est considéré comme doté d'un nom fort ; les autres assemblys qui font référence au vôtre utiliseront ce nom fort en quatre parties. Concrètement, cela signifie que vous pouvez placer votre assembly dans le GAC et profiter de la stratégie de gestion des versions.

Avec toutes ces clés publiques, ces signatures, ces valeurs de hachage, etc., il est facile de perdre de vue les protections dont on bénéficie en réalité. Voici donc ce que le CLR tente de garantir : si vous créez un assembly FOO.DLL et si vous le signez (ce qui lui donne un nom fort), quiconque référence FOO.DLL à partir d'un de ses assemblys au moment de la compilation obtiendra au moment de l'exécution un FOO.DLL que vous aurez également produit (vous ou la personne qui connaît la clé privée derrière le nom fort). Il peut ne pas s'agir exactement du même FOO.DLL, car la stratégie de gestion des versions peut autoriser le CLR à utiliser une version différente à la place de l'original, mais vous avez un minimum de garantie que le code exécuté a bien été produit par la même personne qui a créé le fichier FOO.DLL original. Ceci évite le remplacement de FOO.DLL par une version de type cheval de Troie éventuellement malveillante.

L'implémentation correspondante est la suivante. Lorsque vous compilez un assembly, par exemple BAR.EXE, et qu'il fait référence à un assembly doté d'un nom fort, par exemple FOO.DLL, le compilateur enregistre le nom fort de FOO.DLL dans le manifeste de BAR.EXE. Cet enregistrement inclut une référence à la clé publique (voir la figure 2). Au moment du chargement, outre les contrôles normaux de signatures pour vérifier qu'il n'y a pas eu de modification non autorisée des fichiers binaires de l'assembly, le chargeur vérifie que la clé publique dans FOO.DLL correspond à celle qui est enregistrée dans BAR.EXE. Ainsi, les liens entre les assemblys sont protégés.

strongnames02.gif

Figure 2. Une référence à un assembly au nom fort

Qu'est-ce qui protège BAR.EXE ? Si vous ouvrez un shell de commande et exécutez simplement BAR.EXE en tapant BAR <Entrée>, quelle garantie avez-vous qu'il n'a pas été remplacé par un cheval de Troie ? Dans ce cas, aucune. Pensez-y. Si vous voulez une garantie, vous devez prévoir de fournir au système d'exploitation la bonne clé publique connue pour BAR.EXE. Sinon, le pirate peut signer sa version cheval de Troie de BAR.EXE en utilisant une clé qu'il aura générée de manière aléatoire. Le CLR peut certainement vérifier la cohérence de BAR.EXE (c'est-à-dire que sa signature peut être contrôlée) à partir de la clé publique contenue dans le manifeste. Mais celle-ci n'est pas la clé publique affectée par l'auteur d'origine de BAR.EXE. Pour résoudre ce problème, vous pouvez écrire un petit programme de chargement qui appelle simplement Assembly.Load(), en transmettant les informations de la clé publique avec le nom de l'assembly. Supposons que vous appeliez ce programme LOADER.EXE : vous aurez le même problème en cherchant à vérifier LOADER.EXE.

Imaginez BAR.EXE sous la forme d'une page Microsoft® ASP.NET. Certes, vous pouvez faire référence à un assembly au nom fort à partir de votre page,

<%@assembly name='foo, Version=1.0.0.0,
Culture=neutral,PublicKeyToken=2d7adc3047e7238d'%>

...mais vous n'avez aucun moyen d'indiquer à ASP.NET le nom fort de l'assembly de cette page. Même si vous pouviez nommer celle-ci avec un nom fort, cela ne vous aiderait pas vraiment. En revanche, il est possible de faire référence à un gestionnaire précompilé ou un module enregistré dans web.config ou dans machine.config via un nom fort. Naturellement, cette vérification échoue si le pirate peut modifier le fichier de configuration et remplacer votre clé publique par la sienne.

Il est intéressant de noter un détail de cette implémentation. Dans la référence précédente à l'assembly, vous constatez que nous faisons référence à des assemblys en utilisant un « jeton de clé publique », et non la clé publique complète. Ce jeton est comme une empreinte de la clé publique. Lorsque nous faisons référence à un assembly avec son nom fort, puisque nous utilisons toujours le jeton de clé publique, la meilleure vérification effectuée par le chargeur se limite à nous assurer que l'empreinte spécifiée correspond à la clé publique de l'assembly en cours de chargement. Dans quelle mesure un pirate pourrait-il générer une autre paire de clés RSA (pour laquelle il connaît la clé privée) dont la clé publique aurait la même empreinte que la nôtre ? Un jeton de clé publique est formé à partir des 8 octets faibles du hachage SHA1 de 20 octets de la clé publique. Il existe 2^64 valeurs possibles pour un jeton de 8 octets, ce qui n'a rien pour décourager les pirates puisqu'il est relativement facile avec le matériel actuel de calculer ces 2^64 valeurs possibles. Mais le calcul des paires de clés RSA est un procédé coûteux et l'accomplir 2^64 fois risque d'être irréalisable sans des investissements financiers importants pour acquérir le matériel nécessaire. En d'autres termes, un pirate « moyen » ne peut probablement pas se le permettre tout seul, mais l'agence de renseignements voisine l'a peut-être déjà fait.

Si la vérification des noms forts est une fonction de sécurité qui dépend de vous, votre modèle de protection doit inclure des attaques contre les empreintes, puisque là se trouve le maillon faible. Il n'est pas trop difficile de comprendre pourquoi l'équipe du CLR a opté pour une solution de compromis : la saisie d'un jeton de clé publique de 8 octets nécessite 16 frappes de touches. La saisie du hachage SHA1 complet de 20 octets, qui offre un meilleur niveau de sécurité, en exigerait 40. Mais à quelle fréquence devons-nous saisir des jetons de clé publique ? Pas très souvent, compte tenu de l'existence d'outils tels que le module .NET Framework Configuration. En outre, il suffit d'une personne pour calculer et publier une paire de clés RSA alternative qui a le même jeton de clé publique que, disons, la clé publique de Microsoft. Nous pouvons espérer que la longueur de cette empreinte va s'étendre.

Toute cette théorie sur les empreintes n'a plus aucune valeur si jamais votre clé privée est compromise, ce qui permettrait à un pirate de signer tous les assemblys de son choix pour les utiliser comme chevaux de Troie.

Les noms forts et la stratégie de sécurité .NET

Les assemblys de type cheval de Troie ne sont pas les seuls composants dont vous devez vous méfier si votre clé privée est compromise. Pour remplacer un assembly au nom fort sur votre ordinateur par un cheval de Troie, le pirate a besoin de contourner le processus de vérification des noms forts. Il doit aussi installer du code sur votre machine, ce qui heureusement n'est pas chose facile. Mais il existe une attaque plus directe et plus dangereuse en rapport avec la stratégie de sécurité .NET et qui concerne le référentiel dans lequel sont prises les décisions de sécurisation. La stratégie de sécurité protège votre ordinateur contre le « malware » géré (malicious software, ou logiciel malveillant) qui peut vous parvenir via le réseau. Lors de votre prochaine connexion en tant qu'administrateur, ouvrez l'outil .NET Framework Configuration (à partir du menu Démarrer, sous Outils d'administration) et utilisez-le pour glaner quelques informations sur la stratégie de sécurité du runtime. Si vous développez entièrement l'arborescence des groupes de codes sous le niveau de stratégie de l'ordinateur, vous verrez que la stratégie de sécurité accorde parfois sa confiance sur la base des noms forts. Par exemple, le groupe de codes appelé Microsoft_Strong_Name accorde la confiance totale à tous les codes installés localement, signés avec une clé spéciale que possède Microsoft. Cette clé sert à signer les principaux assemblys qui constituent .NET Framework lui-même.

Lorsqu'une organisation adopte .NET Framework et commence à utiliser des fonctionnalités telles que le déploiement de type no-touch, vous pouvez être sûr que la stratégie de sécurité va de plus en plus s'appuyer sur des noms forts. Il semble douteux que les éditeurs se mettent à écrire des programmes qui s'exécutent dans des environnements partiellement sécurisés, aussi attendez-vous à une stratégie de sécurité qui accorde la confiance totale sur la base des noms forts internes de l'entreprise. Heureusement, lorsqu'un nom fort est spécifié dans le cadre de la stratégie sécuritaire, la clé publique complète, et pas seulement l'empreinte, est fournie. Toutefois, vous n'en retirerez aucune aide si un pirate a volé votre clé privée. Une fois qu'il l'a en main, il peut signer n'importe quel code de son choix et lui donner votre nom fort.

Le scénario suivant illustre bien cette menace. Supposons que vous ayez une application Windows Forms qui fonctionne comme client lourd pour un service Web. Pour des raisons pratiques, vous avez publié ce client lourd via un déploiement no-touch. Les utilisateurs cliquent simplement sur un lien dans leur navigateur et obtiennent la dernière version du client, sans aucune difficulté. Mais il arrive que le client lance un appel P/Invoke pour accéder à un code hérité, ce qui génère une exception en raison de la manière dont vous avez déployé le client. Lorsqu'un code est téléchargé depuis le réseau, il est considéré comme code mobile et, par défaut, il ne sera pas jugé assez fiable pour appeler directement du code non géré. Pour résoudre ce problème, imaginons que vous avez modifié la stratégie de sécurité .NET dans votre organisation de telle sorte que la confiance totale soit accordée à tous les assemblys dotés de votre nom fort. La figure 3 illustre cette situation.

strongnames03.gif

Figure 3. Octroi de la confiance totale sur la base d'un nom fort

Dans ce scénario, si un pirate a volé votre clé privée, il peut publier sur son site Web du code contenant votre nom fort. S'il arrive à convaincre quelqu'un dans votre entreprise de cliquer sur un lien qui pointe sur son code, ce dernier va s'exécuter avec toutes les autorisations de la personne qui a cliqué sur le lien, silencieusement et sans avertissement. Plutôt angoissant, n'est-ce pas ? Des scénarios comme celui-ci montrent bien à quel point il est crucial de protéger vos clés privées.

Notez que ceci s'applique à tous les autres types de clés privées, y compris celles utilisées pour établir des signatures Authenticode. Imaginez le même scénario, où une signature Authenticode (qui se traduit en preuve d'éditeur dans la stratégie de sécurité .NET) aurait été utilisée pour accorder une confiance totale au lieu d'un nom fort. Les dangers sont essentiellement les mêmes.

Vous pouvez établir une défense à un niveau plus profond, en plaçant le groupe de codes ACME_Strong_Name non pas à la racine mais sous le groupe LocalIntranet_Zone (voir la figure 3), de même que le groupe Microsoft_Strong_Name est placé sous My_Computer_Zone. Les groupes de codes enfants ne sont pas évalués, à moins que leurs parents correspondent ; aussi, en procédant de cette manière, vous créez une stratégie selon laquelle deux conditions doivent être remplies pour que la confiance totale soit accordée : l'assembly doit être chargé depuis la zone LocalIntranet et il doit avoir votre nom fort. Naturellement, vous devez désormais prêter attention aux attaques venant de l'intérieur et qui constituent une menace plus grave que ne l'admettent la plupart des entreprises.

On n'insistera jamais assez sur ce point. Si vous utilisez des noms forts, vous devez disposer d'un processus sécurisé pour signer vos assemblys ; à défaut, vos clés privées seront vulnérables. Nous examinerons plus loin un processus de ce type, mais voyons d'abord une raison supplémentaire de sécuriser les clés privées.

Clés publiques et versions

La stratégie de gestion des versions est un aspect intéressant à prendre en compte concernant les clés publiques. L'hypothèse est qu'un assembly a toujours la même clé publique, mais sa version peut changer avec le temps. Dans le cadre d'une stratégie de gestion des versions, il existe plusieurs façons pour un administrateur système ou un éditeur de logiciel d'affecter la version d'un assembly à charger dans une application. Mais cette stratégie ne fonctionne que si le nom de l'assembly et sa clé publique sont constants. En d'autres termes, vous n'allez pas publier une version 1 de l'assembly FOO avec une clé publique, puis publier la version 2 de FOO en utilisant une deuxième clé publique. Les clés publiques des assemblys sont des choix à long terme : une fois que vous en avez adopté une et que vous l'appliquez à un assembly, vous devez la garder le temps que durera cet assembly, à travers toutes ses versions.

Cette démarche va à l'encontre du principe de révocation d'une clé, qui est une notion importante dans les systèmes d'infrastructure à clé publique, ou PKI. L'idée est que si vous perdez votre clé privée ou si vous la croyez compromise, vous pouvez révoquer la clé publique et en acquérir une nouvelle (pour être honnête, ce n'est pas aussi simple qu'il y paraît). Mais dans le CLR, si vous utilisez une stratégie de gestion des versions et le GAC pour gérer des assemblys communs à plusieurs applications, il est catastrophique du point de vue de la gestion des versions et de la compatibilité que votre clé privée soit compromise. Si vous émettez une nouvelle paire de clés, vous devrez recompiler toutes les applications qui utilisaient jusque-là votre ancienne clé publique. Il n'existe dans ce cas aucun moyen sûr de mise à niveau, ce qui est une autre bonne raison pour protéger votre clé privée.

Si la sécurité est un réel souci pour vous (et elle doit l'être), vous investirez dans un matériel permettant de stocker les clés hors ligne. Il s'agit d'une carte à puce, et je vois là une excellente idée pour un futur article dans le cadre de cette rubrique. Mais en attendant, voici comment appliquer dès maintenant une technique dite de signature différée.

Utilisation de la signature différée pour réduire les risques

Une des solutions les plus faciles pour réduire les menaces qui pèsent sur vos clés privées est de procéder à la signature différée de vos assemblys. Cette technique permet au compilateur de créer un assembly sans connaître votre clé privée ; il n'a besoin que de la clé publique, qui n'est pas un secret. En voici le fonctionnement.

La première étape consiste à générer une ou plusieurs paires de clés RSA pour vos noms forts. En attendant la livraison de votre carte à puce (que vous avez commandée, n'est-ce pas ?), vous devrez les stocker dans le système de fichiers ; faites-le donc sur un ordinateur fiable qui n'est pas connecté au réseau. Pour générer chaque paire de clés RSA, utilisez la commande suivante :

sn -k pubpriv

L'outil SN va créer une paire de clés dans un fichier appelé pubpriv. Exécutez immédiatement la commande suivante pour copier uniquement la clé publique dans un second fichier appelé pub :

sn -p pubpriv pub

Copiez pub sur un support amovible et placez-le sur une autre machine. Supprimez ensuite pubpriv de l'ordinateur et placez-le dans un coffre ou un lieu tout à fait sûr. Vous n'aurez besoin de ce fichier que pour envoyer votre premier assembly signé à une personne extérieure à votre groupe. Distribuez le fichier pub à quiconque a besoin de compiler vos assemblys. Cette clé n'est pas secrète, aussi ne vous inquiétez pas d'un vol éventuel.

Suivez cette procédure pour chaque paire de clés que vous envisagez d'utiliser sur des noms forts. Une fois que toutes les clés privées sont en lieu sûr, coffre ou chambre forte, et lorsque vous êtes sûr qu'elles ne seront pas perdues, endommagées ou volées, détruisez le disque dur de l'ordinateur sur lequel vous les avez générées. Je ne plaisante pas : réduisez-le à néant. Naturellement, si vous aviez utilisé une carte à puce, vous n'auriez pas stocké de clé privée sur un disque dur, ce qui aurait simplifié le stockage et la maintenance (au fait, avez-vous reçu votre carte à puce ?).

Pour procéder à la signature différée d'un assembly, utilisez l'attribut AssemblyKeyFile pour faire référence au fichier pub, qui doit se trouver sur la machine de chaque développeur susceptible d'en avoir besoin. Appliquez aussi l'attribut AssemblyDelaySign comme suit :

[assembly: AssemblyKeyFile(@"c:\keys\pub")]
[assembly: AssemblyDelaySign(true)]

Le compilateur sait ainsi qu'il doit incorporer la clé publique dans l'assembly qu'il crée, mais sans se soucier de générer la signature. Le compilateur garde dans l'assembly une place pour l'ajout ultérieur de la signature.

Enfin, sur les machines utilisées pour tester ces assemblys non signés, indiquez au CLR d'ignorer la vérification des noms forts pour la clé publique correspondante. Pour ce faire, vous aurez besoin du jeton de votre clé publique, l'empreinte dont nous avons parlé plus haut. Pour l'identifier, utilisez la commande suivante :

sn –t pub

Supposons que le jeton de clé publique soit bc19568c6e03e7e6. Voici comment l'enregistrer pour ignorer la vérification sur un ordinateur :

sn -Vr *,bc19568c6e03e7e6

Ceci évite que le CLR tente de vérifier la signature des assemblys associés au jeton de clé publique ci-dessus, soit au moment du chargement, soit lorsque vous les installez dans le GAC.

Lorsque vous êtes prêt à envoyer un assembly à une personne extérieure à votre équipe de développeurs, placez-le après l'avoir compilé sur une machine sécurisée, récupérez le fichier pubpriv du coffre où il se trouve, installez-le sur la machine et exécutez la commande suivante :

sn -R assemblyfile

Cette commande utilise la clé privée pour signer l'assembly, remplissant l'espace laissé par le compilateur. Et encore un disque dur à réduire à néant ! Finalement, les cartes à puce ne sont pas une mauvaise idée...

Si vous ne voulez pas entendre parler de la destruction de disque dur et si, pour une raison ou une autre, vous ne voulez pas dépenser 100 dollars pour une carte à puce, utilisez un disque virtuel chaque fois que vous devez stocker provisoirement des clés privées. En effet, le redémarrage de l'ordinateur garantit que ces données confidentielles ne sont plus accessibles, sauf aux pirates les plus coriaces et les mieux équipés.

Protection de votre équipe de développeurs

La signature différée n'est pas parfaite. Compte tenu que vos concepteurs doivent désactiver la vérification des noms forts pour une ou plusieurs clés publiques afin de tester leurs propres assemblys durant le développement, veillez particulièrement à ne pas accorder la confiance à un assembly sur la seule présence d'un de ces noms forts non vérifiés. Avec votre clé publique, un pirate peut signer en différé un assembly malveillant aussi facilement que vous procédez à la signature différée de vos propres assemblys. Partez également du principe que les pirates sont capables d'obtenir facilement votre clé publique, puisqu'elle est incorporée sous forme de métadonnées dans chaque assembly au nom fort que vous générez.

Veillez à bien informer votre équipe de développeurs de ce problème. Si vous avez besoin d'identifier vos assemblys dans le cadre de la stratégie de sécurité .NET, n'utilisez pas un nom fort non vérifié car un utilisateur malveillant peut sans difficulté le transformer en cheval de Troie, comme nous l'avons vu plus haut. Une autre solution serait d'utiliser un certificat temporaire, émis en interne, de signature de code (le bon vieux système Authenticode) dont la clé privée est connue de l'équipe et sert à signer tous les assemblys. Dans ce cas, vous utilisez une preuve d'éditeur au lieu d'une preuve de nom fort pour identifier vos assemblys durant le développement et les tests.

Conclusion

Les noms forts sont très puissants, mais la puissance suppose une grande responsabilité. La capacité du CLR à offrir des garanties sécuritaires sur la base de noms forts n'est pas meilleure que la protection que nous mettons en place autour de nos clés privées. Alors, protégeons ces clés.

Haut de page Haut de page 

 



Dernière mise à jour le lundi 8 mars 2004



Afficher:
© 2014 Microsoft