Wednesday, April 14, 2010

Using MEF with Signed Part Assemblies

Over the past few months, I've been giving a number of presentations over the Managed Extensibility Framework. Every time I've given the presentation, the same question has came up - "How was signed assemblies and security factor into MEF?".  Ultimately, I didn't have an answer at that time since my experience with the need to do assembly signing has been limited, and my perception is very trusting when it comes to plug-ins and extensibility.  Regardless, I don't like not having an answer on a subject I'm presenting/teaching.  So, in this post, we'll look at the challenges that exist to ensure that any assemblies that contains parts is signed and how to control composition of those assemblies in your MEF-enabled application.

Important Links

How To Check If An Assembly Is Signed?

While I have said that my experience with signed assemblies has been limited, I have always created or consumed signed assemblies.  This was great when adding references but the point of using MEF is to ensure we don't have to add references and recompile.  How do I check if an assembly is signed without having to add a reference and recompile the code?  After doing a little digging, I wrote the following unit test:

   1:  [Test]
   2:  public void Check_Unsigned_Assembly()
   3:  {
   4:      var assembly = Assembly.GetExecutingAssembly();
   5:   
   6:      Assert.IsEmpty(assembly.GetName().GetPublicKey());
   7:  }
   8:   
   9:  [Test]
  10:  public void Check_Signed_Assembly()
  11:  {
  12:      var assembly = Assembly.LoadFile(ASSEMBLY_ONE_PATH);
  13:   
  14:      Assert.IsNotEmpty(assembly.GetName().GetPublicKey());
  15:  }

In the first test we are loading the test assembly which isn't signed; however, the second test uses a signed assembly. In these tests we are checking the assemblies' AssemblyName by calling GetName() and then GetPublicKey().  If the PublicKey values, which is a byte array (byte[]), is empty, then the assembly is not signed.  That's easy; however, if 2+ assemblies are signed with the same .snk file are compared, do they have the same PublicKey value?

   1:  [Test]
   2:  public void Check_Signed_Assemblies_For_Same_Key()
   3:  {
   4:      var assembly1 = Assembly.LoadFile(ASSEMBLY_ONE_PATH);
   5:      var assembly2 = Assembly.LoadFile(ASSEMBLY_TWO_PATH);
   6:   
   7:      CollectionAssert.AreEqual(assembly1.GetName().GetPublicKey(), 
   8:                                  assembly2.GetName().GetPublicKey());
   9:  }

Short answer....yes, the keys are the same.  Now that we know how to test for signed assemblies and know that assemblies that are signed with the same key have the same PublicKey byte array value, let's look at how we can push this into our MEF-ed out application.

Validating Part Assemblies Are Signed

There are a few ways we can apply the logic we explored using the unit tests above. We could loop through a directory of assemblies and validate the assemblies prior to adding each one as the target of an AssemblyCatalog for an AggregateCatalog.  Ideally, we would probably want to wrap that logic as a custom, extended catalog in order to make it reusable if this is something you'll need more often.  For this demo though, let's just keep everything local to our application through the below method:

   1:  private void Compose()
   2:  {
   3:      var key = Assembly.GetExecutingAssembly().GetName().GetPublicKey();
   4:   
   5:      var catalog = new AggregateCatalog();
   6:      catalog.Catalogs.Add(new AssemblyCatalog(Assembly.GetExecutingAssembly()));
   7:   
   8:      var dir = new DirectoryInfo("Plugins");
   9:      Assembly assembly;
  10:   
  11:      foreach (var file in dir.GetFiles("*.dll"))
  12:      {
  13:          assembly = Assembly.LoadFile(file.FullName);
  14:          byte[] assemblykey = assembly.GetName().GetPublicKey();
  15:   
  16:          // custom compare method
  17:          if (ArraysEqual(key, assemblykey))
  18:          {
  19:              catalog.Catalogs.Add(new AssemblyCatalog(assembly));
  20:          }
  21:      }
  22:   
  23:      var container = new CompositionContainer(catalog);
  24:      container.ComposeParts(this);
  25:  }

In this method, we're adding AssemblyCatalogs into an AggregateCatalog only if a located assembly is signed. If the assembly is not signed, we're not going to load it for this particular composition.  Other parts of the application may use all assemblies but for this method, we only want to be signed assemblies that have the same key as the executing assembly.  Sounds simple enough, right? Well, there are a few things you need to do to make this work.

A Caveat to Implementation

The first thing I ran into when I tried to implement this with MEF Preview 9 out on Codeplex was that the MEF assembly isn't signed.  This wouldn't be an issue except for the fact that any signed assemblies cannot reference unsigned assemblies. So, if you expose a public assembly that contains a custom ExportAttribute or a base class/interface that is decorated with the InheritedExportAttribute, you'll need to have that assembly signed so it can be used by those extending the application.  Since that assembly is going to be signed, then it needs a signed version of MEF.  It's a vicious cycle. Once you have your core application signed (since it needs to referenced your signed public assembly), you're good to go.  In the accompanying C# project, I created a signed version of the MEF Preview 9 source and referenced it in each project that needed such.

Summary

While this is more proof of concept code, it hopefully illustrates what can be done in order to verify not only how to selectively load assemblies that contain MEF parts based on if they are signed or not as well as dependent on the key too.  Like I said before, the above code could be refactored into a custom and/or extended catalog in order to simplify the logic in the Compose() method as well as advocate reusability.


Update:

After passing this post around, I was informed that checking to see if the assembly is signed with a public key and/or that if the key matches an existing key is not enough for security sake. To take it another step further, please check out the below blog post by Andreas Håkansson (The Code Junkie) that describes how to test for a strong name as well.


Shout it

2 comments:

  1. Thank's for great topic, very helpfull and very clear.

    Keep up the great work.

    ReplyDelete
  2. by the way can yor get the assembly thathave been loaded into the aggregated catalog. If not, how can I achieve this. What I need is a system where I can placed my loaded assemblies into an assembly catalog, with thier type and directory.
    The get the actually "assmebly" so to be able to invoke it? Any suggestions would help :)

    ReplyDelete