次の方法で共有


CertificateInstaller

In what is increasingly beginning to look like a series of posts on using Installers for fixture setup, this time I'll be revisiting my old post on performing integration testing with certificates. As it turns out, installing and subsequently removing X509 certificates from the certificate store are simply fixture setup and teardown operations, and once you get into that mode of thought, such operations are very well modeled by Installers. In this post, I'll reimplement the CertificateManager class from the original post in a new class called CertificateInstaller.

Each CertificateInstaller instance manages setup and teardown for a single certificate:

 public class CertificateInstaller : Installer
 {
     private readonly string filePath_;
     private readonly string password_;
     private readonly StoreLocation storeLocation_;
     private readonly StoreName storeName_;
     private readonly string subject_;
  
     public CertificateInstaller(string filePath,
         string subject,
         string password,
         StoreName sn,
         StoreLocation sl)
     {
         this.filePath_ = filePath;
         this.subject_ = subject;
         this.password_ = password;
         this.storeName_ = sn;
         this.storeLocation_ = sl;
     } 
 }

To set up the certificate, information about the certificate, such as the path to the .pfx file, the password with which to import it, the store name and location to place it, etc. must be supplied. With that information ready, installing the certificate is done by overriding the Install method:

 public override void Install(IDictionary stateSaver)
 {
     base.Install(stateSaver);
  
     X509Certificate2 cert =
         new X509Certificate2(this.filePath_,
             this.password_, X509KeyStorageFlags.PersistKeySet);
  
     X509Store store = new X509Store(this.storeName_,
         this.storeLocation_);
     try
     {
         store.Open(OpenFlags.ReadWrite | OpenFlags.OpenExistingOnly);
         store.Add(cert);
     }
     finally
     {
         store.Close();
     }
 }

The certificate is simply being read from the .pfx file (including its private key) and added to the configured certificate store.

Likewise, removing the certificate is being done by overriding the Uninstall method:

 public override void Uninstall(IDictionary savedState)
 {
     base.Uninstall(savedState);
  
     X509Store store = new X509Store(this.storeName_,
         this.storeLocation_);
     try
     {
         store.Open(OpenFlags.ReadWrite | OpenFlags.OpenExistingOnly);
         var certificatesToDelete =
             from X509Certificate2 cert in store.Certificates
             where cert.Subject == this.subject_
             select cert;
         foreach (X509Certificate2 c in certificatesToDelete)
         {
             store.Remove(c);
         }
     }
     finally
     {
         store.Close();
     }
 }

The specified certificate store is simply opened, and all certificates with the configured subject are removed. Nothing too fancy is going on here, other than I rewrote the original certificate deletion logic to take advantage of LINQ to Objects just for the fun of it; if you need to do this on .NET 2.0, you can use code similar to that in my original post.

With the CertificateInstaller class in place, I prefer to create a test-specific installer to define the immutable shared fixture:

 internal class FixtureInstaller : Installer
 {
     internal FixtureInstaller()
     {
         this.Installers.Add(new CertificateInstaller(
             "MyServer.pfx", "CN=MyServer", "ploeh",
             StoreName.My, StoreLocation.LocalMachine));
         this.Installers.Add(new CertificateInstaller(
             "MyClient.pfx", "CN=MyClient", "ploeh",
             StoreName.My, StoreLocation.LocalMachine));
         this.Installers.Add(new CertificateInstaller(
             "MyServer.pfx", "CN=MyServer", "ploeh",
             StoreName.TrustedPeople, StoreLocation.LocalMachine));
         this.Installers.Add(new CertificateInstaller(
             "MyClient.pfx", "CN=MyClient", "ploeh",
             StoreName.TrustedPeople, StoreLocation.LocalMachine));
     }
 }

This test-specific Installer serves as a simple container for my four CertificateInstaller instances - one for each certificate I need to install for my test.

Given that I have defined a static FixtureInstaller member variable called immutableFixtureInstaller_, setting up the immutable shared fixture is easy:

 [ClassInitialize]
public static void InitializeClass(TestContext ctx)
{
    MyServiceTest.immutableFixtureInstaller_ = new FixtureInstaller();
    MyServiceTest.immutableFixtureInstaller_.Uninstall(null);
    MyServiceTest.immutableFixtureInstaller_.Install(new Hashtable());
}

If a previous test run never completed, it may have left some orphaned certificates in one or more of the certificate stores, so I'm calling Uninstall before Install to ensure that any such orphans will be removed first.

Teardown is even simpler:

 [ClassCleanup]
 public static void CleanupClass()
 {
     MyServiceTest.immutableFixtureInstaller_.Uninstall(null);
 }

Compared to my original post on integration testing with certificates, none of the functionality described here is new, but I still think that implementing it as an Installer provides a lot of benefit, since it is now possible to quickly compose it with other Installers to build a complex Installer for your test fixture (or maybe even reuse an Installer defined by the SUT itself).

Comments