Salut à tous,
Suite à mon topic d'hier sur comment vérifier la signature d'une dll que l'on charge dynamiquement (par reflexion donc) ;

J'ai cherché une solution pour effectuer l'opération inverse : Verifier la signature/type de celui qui nous appelle. Clairement, le but de la manip est de rendre notre Dll utilisable uniquement par un module bien défini et signé (lire le NB ci dessous).

Voici la solution que j'ai trouvé, n'hesitez pas à me reprendre si je dis des bétises.

NB: Rien n'empechera une personne mal intentionnée de récupérer le code de notre dll, hoter ce mecanisme de protection, puis recompiler une dll sous le meme nom/type. Mais cette personne ne pourra pas réutiliser votre signature pour "certifier" sa dll. Du coup si on implémente la protection (vérifier la signature) des 2 cotés (l'appellant et l'appellé), on s'assure que ni l'un ni l'autre n'a été remplacé par un faux (spoofé), meme si le code reste totalement accessible par reflection.
Solution officielle qui ne fonctionne pas !
D'apres MSDN (source), l'attribut System.Security.Permissions.StrongNameIdentityPermissionAttribute permet (entre autre) de protéger votre code pour que l'assembly appellant ce dernier réponde à certains critères. Ainsi leur exemple est supposé faire en sorte que seule une assembly avec la bonne signature puisse executer le code :

Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
 
[assembly: StrongNameIdentityPermissionAttribute(SecurityAction.RequestMinimum,
 PublicKey = "00240000048000009400000006020000002400005253413100040000010001005" +
 "38a4a19382e9429cf516dcf1399facdccca092a06442efaf9ecaca33457be26ee0073c6bde5" +
 "1fe0873666a62459581669b510ae1e84bef6bcb1aff7957237279d8b7e0e25b71ad39df3684" +
 "5b7db60382c8eb73f289823578d33c09e48d0d2f90ed4541e1438008142ef714bfe604c41a4" +
 "957a4f6e6ab36b9715ec57625904c6")]
namespace Plugin
{ 
   //Seule l'assembly avec la signature définie dans l'attribut peut appeller ce code
    public class MaClasse
   {
   }
}
J'ai testé cette methode et simplement : ca ne fonctionne pas du tout !!! J'ai fais un bout de code non signé, qui charge mon assembly Plugin par reflection puis instancie MaClasse. Non seulement aucune exception n'a été levée, mais en plus j'ai pu instancier MaClasse sans aucun soucis. On est donc totalement passé au travers de la sécurité souhaitée => corbeille => vider la corbeille

La solution que j'ai trouvé est du bricolage de haute voltige et voici son principe :

Dans le constructeur de MaClasse, je récupere le StackTrace, puis remonte la pile d'appel à la recherche du Type souhaité.
Je m'attends donc en début de pile à tomber sur le Type actuel (Plugin.Class1) qui est dans l'assembly actuelle (Assembly.GetExecutingAssembly()).
Ensuite je peux éventuellement (dans le cas de reflection) passer par mscolib.
Et enfin je m'attends à tomber sur l'assembly souhaitée (dans mon cas le type "PluginLoader.Loader" qui est dans l'assembly PluginLoader.dll).

Avant tout chose, il faut récupérer la clé publique de PluginLoader.dll qui se trouve dans son StrongName.


-Lancer "Visual Studio xxxx Command Prompt"
-Aller dans le repertoire de la PluginLoader.dll (l'appellant) puis taper la commande suivante :

secutil -array -cmode -strongname PluginLoader.dll > resultat.txt
-Ouvrir resultat.txt et y récupérer le tableau de byte qui représente la clé publique de cette signature.

Voici maintenant le code du coté de la Dll appellée.
Notez la présence de byte[] expectedCaller qui contient la clé publique que l'on vient de récupérer.
Le reste se passe dans le constructeur de Class1 que j'ai commenté :
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
 
namespace Plugin
{
 
    public class Class1 : IPluginTest.IPlugin
    {
 
        private byte[] expectedCaller = { 0, 36, 0, 0, 4, 128, 0, 0, 148, 0, 0, 0, 6, 2, 0, 0, 0, 36, 0, 0, 82, 83, 65, 49, 0, 4, 0, 0, 1, 0, 1, 0, 85, 17, 102, 219, 117, 131, 17, 136, 216, 200, 146, 49, 192, 239, 215, 17, 5, 196, 87, 123, 112, 25, 169, 223, 88, 124, 97, 245, 223, 106, 1, 21, 173, 217, 185, 208, 154, 181, 15, 127, 140, 120, 49, 190, 2, 147, 97, 105, 250, 34, 9, 206, 18, 88, 174, 126, 34, 13, 23, 100, 44, 14, 233, 137, 58, 121, 172, 104, 182, 170, 2, 242, 78, 224, 104, 9, 39, 184, 59, 66, 188, 68, 114, 86, 24, 22, 165, 185, 103, 192, 63, 47, 166, 137, 68, 108, 243, 17, 255, 182, 134, 222, 40, 35, 214, 157, 59, 179, 161, 137, 233, 37, 224, 53, 154, 14, 107, 29, 185, 4, 30, 106, 116, 114, 64, 191, 13, 151 };
 
 
        public Class1()
        {
            //On recupere le StackTrace.
            StackFrame[] stackhisto = new StackTrace().GetFrames();
            //Type récupéré dans le Stack (voir foreach plus bas)
            Type callerType = null;
            //Assembly du FrameWork .NET
            Assembly NetFramework = Assembly.Load("mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089");
 
            //On remonte le stack pour s'assurer que c'est bien notre loader qui vient de nous charger.
            //Si on trouve ce que l'on cherche callerType sera initialisé avec le Type de celui qui nous appelle (PluginLoader.Loader), sinon il sera null.
            foreach (StackFrame item in stackhisto)
            {
                callerType = item.GetMethod().DeclaringType; //Quel type définit la methode qui m'appelle?
 
                if (callerType.FullName != "PluginLoader.Loader" && //Si ce type n'est pas celui attendu
                     Assembly.GetAssembly(callerType) != Assembly.GetExecutingAssembly() && //et que son assembly n'est pas l'assembly actuelle (Plugin.Class1, version=1.0.0.0 ...
                     Assembly.GetAssembly(callerType) != NetFramework //et que son assembly n'est pas non plus cette du framework
                   )
                {
                    //Alors j'estime que l'on a injecté une cochonerie entre mon plugin et l'appellant attendu (PluginLoader.Loader) donc pas la peine d'aller plus loin.
                    callerType = null;
                    break;
                }
                //Si c'est bien le Loader, on sort et callerType est le Type du loader.
                if (callerType.FullName == "PluginLoader.Loader")
                    break;
                else //Sinon on continu, mais fixons callerType à null au cas où on est dans la derniere passe du foreach.
                    callerType = null;
            }
            if(callerType == null) //Voir commentaires du foreach
                throw new SecurityException("Ce plugin ne peut etre appellé que par notre superbe logiciel !");
 
            //On va analyser l'assembly de l'appellant pour s'assurer que la signature corresponde
            Assembly caller = Assembly.GetAssembly(callerType);
            bool hasname = caller.GetName() != null; //Ne devrait pas se produire.
            bool haskey = hasname && caller.GetName().GetPublicKey() != null; //Si l'assembly n'est pas signée, il n'y aura pas de public Key.
            bool IsCallerOk = haskey && expectedCaller.SequenceEqual(caller.GetName().GetPublicKey()); //Est ce que la clé publique correspond à ce que l'on attends?
 
            if (!IsCallerOk)
                throw new SecurityException(String.Format("Ce plugin ne peut etre appellé que par notre super logiciel !\r\nname : {0}\r\nkeypresent : {1}\r\nkeyvalid : {2}",hasname,haskey,IsCallerOk));
        }
        #region IPlugin Members
 
        [IntelliLock.Obfuscation(Exclude = true, ApplyToMembers = true)]
        public void Run()
        {
            Console.WriteLine("Je suis {0}",Assembly.GetAssembly(typeof(Class1)).FullName);
        }
 
        #endregion
    }
}
Merci