Signature des assemblies par nom fort en 5 min

La signature des assemblies par nom fort est un procédé permettant d’assurer l’unicité d’une assembly. Il ne faut pas confondre la signature par nom fort (i.e. Strong Name signing) avec la signature électronique (i.e. digital signature).

Si on souhaite sécuriser le contenu d’une assembly et assurer qu’elle n’a pas été altérée de façon malveillante il faut plutôt se diriger vers des solutions de signature électronique comme Authenticode.

Explications

Intérêts de la signature par nom fort

Le but principal de la signature par nom fort est d’identifier une assembly de façon unique:

  • Nom unique: une assembly signée par nom fort possède un nom unique qui permet de la rendre unique. Elle peut, ainsi, être partagée entre plusieurs applications. Le but de ce nom unique est d’éviter des collisions de namespaces si accidentellement une autre assembly est nommée de la même façon. Si on a une dépendance vers une assembly avec un nom fort, cette dépendance ne pourra pas être confondue avec une autre assembly du même nom.
  • Ajouter l’assembly dans le GAC: pour ajouter une assembly dans le GAC (Global Assembly Cache), elle doit obligatoirement être signée. L’intérêt principal d’ajouter une assembly dans le GAC est de la partager entre plusieurs applications.

La signature par nom fort est insuffisante pour:

  • Garantir l’identité de l’éditeur de l’assembly: la signature par nom fort ne peut pas à elle-seule garantir l’identité de la personne qui fournit une assembly. Ce n’est pas parce qu’une assembly est signée de cette façon qu’on peut être sûr qu’elle provient de la bonne personne.
  • Empêcher la corruption de l’assembly: de la même façon la signature par nom fort ne permet pas de garantir qu’une assembly n’a pas été corrompue. Si au chargement de l’assembly, la vérification de la signature par nom fort échoue, on aura un indice que l’assembly n’est pas valide. Toutefois si la vérification réussie, on n’est pas sûr que ‘assembly n’a pas été modifiée.

Algorithme de cryptographie asymétrique

La signature par nom fort repose sur le principe de la cryptographie asymétrique. Ce principe se base sur 2 concepts: le hashage et la signature électronique.

Hashage

A partir du fichier correspondant à une assembly, l’algorithme de hashage est capable d’obtenir une donnée plus petite et de taille fixe. Le résultat du hashage appelé le hash ne permet pas de revenir à l’assembly d’origine, le hashage est irréversible.

Si 2 hash sont obtenus à partir de 2 assemblies et si ces hash sont identiques alors les 2 assemblies sont identiques. A l’opposé si les hash sont différents alors les assemblies ne sont pas semblables.

Dans le cas de .NET, l’algortihme de hashage utilisé est SHA1.

Signature électronique

La signature nécessite 2 clés: une clé publique et une clé privée. Ces 2 clés sont dépendantes:

  • Si une clé est utilisée pour encrypter des données, l’autre clé doit être utilisée pour décrypter les données. Il n’est pas possible d’utiliser une autre clé pour décrypter les données.
  • Si on a la clé publique, il n’est pas possible de trouver la clé privée correspondante.
  • Si un tiers possède la clé privée, il est capable de générer des données encryptées et éventuellement usurper l’identité de la personne à qui appartient les clés. La clé privée doit donc être gardée secrète et si elle est dévoilée, on ne peut plus utiliser la clé publique correspondante. La clé publique, quant à elle, peut être dévoilée mais ne suffit pas à elle seule à encrypter puis décrypter les données.
  • Il est difficile de générer une paire de clés identiques.

Procédé de signature des assemblies

Le but de ce procédé est de garantir que:

  • Le contenu d’une assembly n’a été modifiée,
  • L’identité de la personne qui fournit l’assembly.
Procédé de signature par nom fort

La personne qui fournit l’assembly effectue les étapes suivantes:

  1. Il génère une paire de clés: la clé privée et la clé publique.
  2. Quand l’assembly est générée, il exécute l’algorithme de hashage sur le fichier et obtient ainsi un hash.
  3. Le hash est encrypté en utilisant la clé privée.
  4. Il ajoute à l’assembly une signature électronique contenant le hash encrypté et la clé publique.
  5. Il envoie l’assembly. Cet envoie peut être fait par des moyens non sécurisés.

La personne qui reçoit l’assembly effectue les étapes suivantes:

  1. Il exécute l’algorithme de hashage sur l’assembly reçu et obtient un hash.
  2. Il décrypte le hash contenu dans la signature de l’assembly en utilisant la clé publique.
  3. Il compare le hash qu’il a calculé et le hash décrypté:
    • Si les 2 hash sont identiques alors l’assembly est valide. Toutefois ça ne veut pas dire que l’assembly n’a pas été modifiée et que la personne qui fournit l’assembly est bien celle qu’on croit.
    • Si les 2 hash sont différents, alors l’assembly n’est pas celle d’origine (elle n’est pas la même ou elle a été modifiée).

A ce stade, on ne peut pas conclure qu’une assembly valide du point de vue de ce procédé n’a pas été modifiée et que l’identité de la personne qui fournit l’assembly est bien identifiée pour plusieurs raisons:

  • Rien ne différencie l’assembly fournie par une personne identifiée d’une assembly fournie par un tiers: si la personne identifiée et tiers signent leur assembly de la même façon (même avec des clés différentes), elles seront toujours considérées comme valides par le receveur.
  • La signature peut avoir été modifiée: il est possible de récupérer l’assembly avant qu’elle ne parvienne au receveur, d’enlever la signature électronique (c’est-à-dire le hash ecnryptée et la clé publique) et d’effectuer une nouvelle signature. On envoie l’assembly avec la nouvelle signature et elle sera considérée comme valide alors qu’on peut très bien, avoir modifié cette assembly.

Juste avec ce type de procédé, on peut seulement garantir qu’une assembly livrée est unique car la paire de clés est unique. Ainsi une assembly signée avec une paire de clé sera différente de la même assembly signée avec une autre paire de clés.

Pour garantir que le contenu de l’assembly n’a pas été altéré et que l’identité de l’éditeur de l’assembly est bien identifié, il faut que la clé publique soit délivrée par une personne ou une autorité de confiance. Cette autorité de confiance certifie que:

  • La clé publique provient bien d’un éditeur précis.
  • Il fournit de façon sécurisée une clé publique non altérée.

Ces 2 éléments permettent de dire:

  • Si on arrive à décrypter le hash dans la signature électronique de l’assembly avec la clé publique alors la clé publique appartient bien à l’éditeur indiqué par l’autorité de confiance.
  • Si le hash décrypté et le hash obtenu après exécution de l’algorithme de hashage sur l’assembly reçue, sont identiques alors l’assembly provient bien de l’éditeur identifié et l’assembly n’a pas été altérée.

Le procédé de signature complête d’une assembly repose sur la confiance qu’on peut avoir envers l’autorité de confiance puisque c’est elle qui garantit l’intégrité de la clé publique.

La signature d’une assembly par nom fort s’arrête à garantir l’unicité d’une assembly car aucune autorité de confiance n’intervient.

Intérêts de la signature par nom fort par rapport à une signature complète

On peut se demander pourquoi utiliser une signature par nom fort si ce procédé ne permet pas de garantir davantage que l’unicité. La signature par nom fort a toutefois quelques avantages:

  • Elle est simple et peu couteuse à mettre en place puisqu’il n’est pas nécessaire de faire appel à une autorité de certification. On peut facilement signer une assembly et la publier sans coût supplémentaire.
  • Si on a pas la possibilité d’être connectée à une autorité de confiance et si on est sûr de la clé publique parce qu’elle a été identifiée au préalable, ce procédé peut suffire à être sûr de l’éditeur de l’assembly.
  • Si la clé publique ne change pas entre 2 versions d’une assembly, si on a confiance dans la première version de l’assembly et si on utilise la même clé publique pour une 2e version alors on peut être sûr que les 2 assemblies proviennent de la même personne. Ce cas de figure suffit dans la plupart des cas et permet, au moins, d’être sûr que l’éditeur d’une assembly n’est pas une autre personne.

Malgré tous ces points, la signature par nom fort est très critiqué notamment avec l’avènement de plateforme open-source comme GitHub. Beaucoup de gens s’interrogent sur l’utilité de ce type de signature alors qu’elle ne permet pas, toute seule, de garantir l’éditeur d’une assembly.
Certaines personnes considèrent qu’à partir du moment où on a le code source d’un projet, on doit pouvoir générer un nouveau projet sans être entravé par une signature par nom fort qui n’est pas très sure:
Still Strong-Naming your Assemblies? You do know it’s 2016, right?

Beaucoup de projets sont disponibles en open-source, par exemple, sur GitHub. Le source de ces projets sont donc facilement visible. Il y a des débats pour savoir s’il faut ou non inclure la clé privée dans les sources du projet:
Sur Stacloverflow: Why is it recommended to include the private key used for assembly signing in open-source repositories?.

Il n’y a pas de meilleures pratiques car on trouve plusieurs pratiques:

  • Inclure la clé privée dans les sources: cette approche signifie que n’importe qui peut signer l’assembly par nom fort avec la même clé que le responsable du projet. Avec cette approche, dans le cas où la clé publique ne change pas entre 2 versions, on n’a plus la garantie que l’assembly provient de la même personne.
    A l’opposé, en incluant la clé privée dans le projet, on peut partager facilement son projet, n’importe qui peut y contribuer, publier une nouvelle assembly et permettre que cette assembly soit éventuellement ajoutée dans le GAC.
  • Ne pas inclure de clé: cette approche est la plus simple, on laisse la possibilité de “forker” le projet et chaque personne est libre d’apposer une signature s’il le désire. De même que l’approche précédente, dans le cas où la clé publique ne change pas entre 2 versions, on perds la garantie de l’éditeur de l’assembly.
  • Utiliser la signature partielle: cette fonctionnalité permet de signer partiellement une assembly. Cette approche est explicitée plus bas.

Signature d’une assembly .NET par nom fort

Pour effectuer la signature, il faut générer une paire de clés.

L’outil utilisé est sn.exe qui est livré avec le developer pack du framework .NET: Developer Pack .NET Framwork 4.7.

Après installation, sn.exe se trouve dans un répertoire similaire à:

C:\Program Files\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.6.1 Tools\sn.exe 

sn.exe est directement accessible à partir de la ligne de commande développeur: “Developer Command Prompt for VS 2017”.

Générer une paire de clés

Il faut exécuter à la ligne de commandes:

sn -k [nom du fichier .SNK]

Le fichier résultat contient la clé privée et la clé publique. Il ne doit pas être dévoilé sinon la signature n’a plus aucun intérêt.

Extraire le clé publique

sn -p [fichier source .SNK] [fichier .SNK résultat avec la clé publique] 

Signer une assembly à la compilation

Pour signer une assembly, on peut ajouter l’attribut suivant dans le fichier AssemblyInfo.cs (le fichier doit être dans le répertoire du fichier .csproj):

[assembly: AssemblyKeyFile(@"privateKey.snk")] 

Ajouter une option au compilateur

Une autre méthode consiste à ajouter l’option suivante au compilateur:

/keyfile:privateKey.snk 

On peut le faire à partir des propriétés du projet dans Visual Studio:

  1. Clique droit sur le projet, puis cliquer sur “Properties”.
  2. Aller dans l’onglet “Signature”
  3. Cliquer sur “Sign the assembly”
  4. Sélectionner le fichier ayant la clé privée

A la compilation, l’assembly sera signée.

Onglet “Signature” dans Visual Studio des propriétés d’un projet

Vérifier la signature d’une assembly

Avec sn.exe
Avec sn.exe, on peut taper la commande suivante pour le jeton public:

sn –T [chemin de l´assembly]

Pour afficher le jeton public et la clé publique:

sn –Tp [chemin de l´assembly] 

Pour vérifier simplement la signature:

sn –vf [chemin de l´assembly] 

Avec ildasm.exe
On peut aussi voir la clé publique avec ILDASM. ILDASM est un outil Microsoft qui permet de désassembler une assembly. Cette outil est livré avec le developer pack. Après installation, il se trouve dans un répertoire similaire à:

C:\Program Files\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.6.1 Tools\ildasm.exe

Pour l’utiliser il faut:

  1. Lancer ildasm.exe
  2. Cliquer sur “File” puis ouvrir le fichier à vérifier
  3. Cliquer sur MANIFEST
  4. La ligne .publickey permet de voir la clé publique:
    Extrait du MANIFEST d’une assembly signée

Conséquences de la signature

Lorsqu’une assembly est signée, toutes ces dépendances doivent être signée. Dans le cas contraire, à l’exécution lorsque le CLR va essayer de charger la dépendance et qu’elle ne possède pas de signature par nom fort, il va lancer une exception:

Unhandled exception : 
System.IO.FileLoadException: Could not load file or assembly ´ClassLibrary1,
Version=1.0.0.0, Culture=neutral, PublicKeyToken=null´ or one of its dependencies.
Strong name signature could not be verified. The assembly may have been
tampered with, or it was delay signed but not fully signed with the correct
private key. (Exception from HRESULT: 0x80131045)

Pour corriger ce problème, il faut aussi signer par nom fort les dépendances de l’exécutable.

A l’opposé, le CLR ne lance pas d’exception si une dépendance est signée et que l’assembly qui l’utilise ne l’est pas.

Enregistrement dans le GAC

Le GAC (i.e. Global Assembly Cache) est un registre dans Windows qui permet de stocker des assemblies qui peuvent être partagées. Si une assembly est enregistrée de façon globale dans le GAC, il n’est plus nécessaire qu’elle soit à coté de l’exécutable au runtime pour être utilisée. A l’exécution, le CLR va vérifier si l’assembly est présente dans le GAC et l’utilise si nécessaire.

Pour enregistrer une assembly dans le GAC elle doit obligatoirement être signée par nom fort.

La manipulation du GAC se fait avec l’utilitaire gacutil livré avec le developer pack. Après installation, il se trouve dans un répertoire similaire à:

C:\Program Files\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.6.1 Tools\gacutil.exe

Il est directement disponible à partir de la ligne de commande développeur: “Developer Command Prompt for VS 2017”.

Il faut exécuter toutes ces commandes avec les droits administrateur.

Enregistrer une assembly dans le GAC:

gacutil /i [chemin de l´assembly] 

Lister les assemblies enregistrées:

gacutil /l 

Désinstaller une assembly du GAC:

gacutil /u [nom de l´assembly] 

Signature partielle par nom fort (delay signing)

Lorsqu’on expose un projet dans une organisation, par exemple dans une entreprise ou sur GitHub, on peut ne pas vouloir montrer la clé privée de façon à garantir qu’on est bien le même éditeur de l’assembly livrée dans le cas où la clé publique reste inchangée.

Si on n’inclue pas la clé privée, quelqu’un qui compile le projet ne pourra pas signer l’assembly. Si cette personne consomme notre assembly comme dépendance et que cette personne a une assembly signée, elle ne pourra pas exécuter le code de notre assembly car le CLR va lancer une exception au chargement.

Une solution à ce problème est de laisser la personne signer elle-même notre assembly. De cette façon elle est libre de signer l’assembly si nécessaire. Une autre approche est de signer partiellement notre assembly pour éviter au consommateur de l’assembly de devoir effectuer ce travail.

La signature partielle doit être utilisée seulement en développement pour faciliter l’utilisation d’une assembly par d’autres développeurs sans compromettre la clé privée. Une assembly partiellement signée ne doit pas être déployée car sur un poste normal elle ne pourra pas être exécutée.

Utiliser seulement la clé publique

Pour signer partiellement une assembly, la clé privée n’est pas nécessaire. La clé privée ne doit pas être exposée donc elle ne doit pas être inclue dans le projet. La signature partielle nécessite seulement la clé publique. C’est la clé publique qui sera inclue, seule, dans le projet.

Pour obtenir la clé publique à partir d’une paire de clé:

sn -p [fichier source .SNK] [fichier .SNK résultat avec la clé publique] 

Signer partiellement une assembly

On peut ajouter les lignes suivantes dans le fichier AssemblyInfo.cs du projet:

[assembly: AssemblyKeyFile(@"publicKey.snk")] 
[assembly: AssemblyDelaySign(true)] 

On utilise seulement la clé publique à ce stade. La clé privée n’est pas nécessaire et ne doit pas être inclue dans le projet.

Une autre possibilité pour signer partiellement à partir de Visual Studio est d’aller dans les propriétés du projet:

  1. Clique droit sur le projet, puis cliquer sur “Properties”.
  2. Aller dans l’onglet “Signature”
  3. Cliquer sur “Sign the assembly”
  4. Sélectionner le fichier ayant la clé publique
  5. Cocher “Delay sign only”:
    Onglet “Signature” dans Visual Studio d’une assemlby signée partiellement
Comportement vis-à-vis des dépendances

Quand une assembly est partiellement signée, toutes ces dépendances doivent être, au moins, partiellement signées c’est-à-dire qu’il est aussi possible d’utiliser des dépendances complètement signées.
En revanche on ne peut pas utiliser des dépendances non signées.

Pour vérifier la signature partielle d’une assembly, on peut utiliser sn.exe:

sn –vf [chemin de l´assembly] 

Exécuter une assembly partiellement signée

La signature partielle par nom fort ne doit être utilisée que lors du développement. Une assembly partiellement signée ne doit pas être déployée c’est la raison pour laquelle l’exécution d’une assembly partiellement signée est, par défaut, interdite.

Si à ce stade, on essaie d’exécuter une assembly partiellement signée, on aura une exception indiquant que le chargement de l’assembly a échoué car la signature par nom fort n’a pas pu être vérifiée:

Unhandled exception : 
System.IO.FileLoadException: Could not load file or assembly ´ConsoleApp1,
Version=1.0.0.0, Culture=neutral, PublicKeyToken=80c65ee63fb3ed24´ or one 
of its dependencies. Strong name signature could not be verified. The assembly may have 
been tampered with, or it was delay signed but not fully signed with the correct private key.
(Exception from HRESULT: 0x8013141A)

Pour résoudre ce problème, il faut autoriser l’exécution des assemblies partiellement signées. Cette autorisation est valable pour une assembly spécifique identifiée par une clé publique.

Les droits administrateurs sont nécessaires pour l’exécution des commandes suivantes.

Pour enregistrer l’assembly pour que la vérification de nom fort soit ignorée:

sn –Vr [chemin de l´assembly] 

L’assembly est identifiée avec sa signature publique donc il n’est pas nécessaire qu’elle soit dans le même répertoire que celui utilisé lors de l’enregistrement.

Dépendances de l’assembly

Pour exécuter une assembly partiellement signée, si elle possède des dépendances partiellement signées, il faut aussi enregistrer les dépendances pour que la vérification de nom fort soit ignorée aussi pour les dépendances.

Pour lister les assemblies pour lesquelles la vérification de nom fort est ignorée:

sn –Vl  

Pour supprimer l’enregistrement de l’assembly pour que la vérification de nom fort soit de nouveau effectuée:

sn –Vu [chemin de l´assembly] 
Différences entre les assemblies 32 et 64 bits

Si on enregistre une assembly compilée pour l’architecture x86, il faut utiliser la version 32 bits de sn.exe. De même pour une assembly compilée en x64, il faut utiliser la version 64 bits de sn.exe.

Sur une machine 64 bits, sn.exe se trouve dans:

  • En 32 bits: C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.6.1 Tools\sn.exe
  • En 64 bits: C:\Program Files\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.6.1 Tools\sn.exe

Si on compile une assembly avec le paramètre “AnyCPU” dans Visual Studio, il faut penser à regarder le paramètre “Prefer 32-bit” dans l’onglet “Build” de Visual Studio pour savoir si l’exécutable sera lancé en 32 ou 64 bits:

Paramètre “Prefer 32-bit” dans Visual Studio

Signer complètement une assembly partiellement signée

Pour déployer une assembly partiellement signée, on peut la signer complètement sans avoir à la recompiler. Cette signature se fait avec la clé privée.

Pour signer une assembly partiellement signée, on peut exécuter la commande:

sn –R [chemin de l´assembly] [chemin de la clé privée] 

Pour vérifier si l’assembly a été correctement resignée, on peut exécuter:

sn –vf [chemin de l´assembly] 
Références:
2 responses... add one

Leave a Reply