Tuesday, February 9, 2010

Add Root CA to Windows Certificate Store using C#

My current client has several laptop users that are not domain joined and, as a result, do not have the Internal Enterprise CA Certificate installed on their machines.  They asked me to write a little application that they can distribute to add the Root CA to these machines.

As always, I look at these projects as an opportunity to learn, and this short piece of code, while not elegant, provided that opportunity in spades.

Two specific areas that are new to me are Code Embedded Files and calling out to an EXE.  I’ll cover both in this blog post as well as provide the full code for sharing.

To start, I created a new Console Application.  I also added the two files that I wanted embedded; CertMgr.EXE and the RootCA.cer file.

When embedding a file, the Build Action of the file must be set to Embedded Resource and, optionally, the Copy to Output Directory can be set to Copy if Newer as illustrated in the screen shot below:

image

I also added the System.Diagnostics and System.Reflection .NET libraries to make calling the Assembly code necessary for unpacking the files. 

using System;
using System.IO;
using System.Diagnostics;
using System.Collections.Generic;
using System.Reflection;
using System.Text;

Next I set a couple string variables that identify a temporary storage location for the files I want to unpack, the certificate file itself, and the .NET Resource Tool CertMgr.EXE that I will use to simplify the installation of the Root CA.

    {
        static void Main(string[] args)
        {
            //set the variable strings
            string store = @"c:\cert";
            string rootFile = @"c:\cert\RootCA.cer";
            string certMgr = @"c:\cert\CertMgr.Exe";

I then move to extract each file from the Assembly package.  Note that the name of the file starts with the name of your application.  In my case, AddRootCA.CertMgr.Exe.  But first I check to see if the file already exists in the location I’ve called.

            //validate CertMgr.exe does not exist
            if (!File.Exists(certMgr))
            {
                //extract CertMgr.EXE from the Assembly
                Assembly certMgrAss = Assembly.GetExecutingAssembly();
                Stream certMgrStrm = certMgrAss.GetManifestResourceStream("AddRootCA.CertMgr.Exe");

I do a little error checking to ensure the stream is created properly.

                if (certMgrStrm == null)
                {
                    Console.WriteLine("Unable to Read CertMgr.Exe file.  Contact your administrator.");
                }

I then check to see if the storage location exists.  If it doesn’t, I create it.

                //Create c:\cert directory for storage
                if (!Directory.Exists(store))
                {
                    Console.Write(store + " does not exist.  Creating.....");
                    Directory.CreateDirectory(store);
                }

Next, I open up the directory and file, in this case the CertMgr.exe and then stream in the binary for the assembly object.

                //open the directory and file for writing
                FileStream certMgrFS = File.OpenWrite(certMgr);
                try
                {
                    // Save the File...
                    byte[] buffer = new byte[certMgrStrm.Length];
                    certMgrStrm.Read(buffer, 0, (int)certMgrStrm.Length);
                    certMgrFS.Write(buffer, 0, buffer.Length);
                    certMgrFS.Close();
                }

Lastly for this section, I catch any exceptions and write them our to the console for review.

                //write any errors to the Console
                catch (Exception ex)
                {
                    Console.WriteLine(ex);
                    Console.WriteLine("An error has occured adding CertMgr.  Press any key to continue...");
                    Console.Read();
                }
            }

The next section of code does virtually the same thing as above with one key exception; it executes the CertMgr.exe file with arguments to install into the certificate into the Local Machine’s Root Certificate Store.

            //validate RootCA.cer does not exist
            if (!File.Exists(rootFile))
            {
                //extract RootCA.cer from the Assembly
                Assembly RootCertAssembly = Assembly.GetExecutingAssembly();
                Stream RootCertStream = RootCertAssembly.GetManifestResourceStream("AddRootCA.RootCA.cer");
                if (RootCertStream == null)
                {
                    Console.WriteLine("Unable to Read RootCA.cer file.  Contact your administrator.");
                }
                //Create c:\cert if it does not exist
                if (!Directory.Exists(store))
                {
                    Console.Write(store + " does not exist.  Creating.....");
                    Directory.CreateDirectory(store);
                }
                //open the directory and file for writing
                FileStream rootFS = File.OpenWrite(rootFile);
                try
                {
                    // Save the File...
                    byte[] buffer = new byte[RootCertStream.Length];
                    RootCertStream.Read(buffer, 0, (int)RootCertStream.Length);
                    rootFS.Write(buffer, 0, buffer.Length);
                    rootFS.Close();
                }
                //catch any errors and write to console
                catch (Exception ex)
                {
                    Console.WriteLine(ex);
                    Console.WriteLine("An error has occurred adding the Root Certificate.  Press any key to continue...");
                    Console.Read();
                }

This is the section of code that calls CertMgr.Exe and passes in the certificate file name and several arguments to install the certificate.  I’m using standard .NET code to instantiate the CertMgr.exe program, but to execute inside the Windows 7/Vista User Account Control security model and on Windows XP machines where the user is not a local administrator, I’ve chosen to execute RunAs.exe as a process verb.  Key in this process call is to ensure UseShellExecute is set to true.  Failure to set this parameter on the process will cause the process to fail without errors.


                //call CertMgr.EXE and add the certificate to the Root Certificate Store
                finally
                {
                    ProcessStartInfo processRoot = new ProcessStartInfo();
                    processRoot.Verb = "runas";
                    processRoot.FileName = certMgr;
                    processRoot.Arguments = "/add c:\\cert\\rootca.cer /c /s /r localMachine Root";
                    processRoot.UseShellExecute = true;
                    Process rootProc = Process.Start(processRoot);
                    rootProc.WaitForExit();
                    rootProc.Dispose();

                }
            }

The last piece of code cleans up the c:\cert directory and all it contents.  And that’s it!

 
            if (Directory.Exists(store))
            {
                try
                {
                    Directory.Delete(store, true);
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex);
                    Console.WriteLine("An error has occured cleaning up.  Press any key to continue...");
                    Console.Read();
                }
            }
            Console.WriteLine("Complete!");
         }
    }
}

Enjoy this little piece of code!

D.

1 comment:

  1. I was looking for a way to do the same thing, turns out there is a native .NET way to do it:
    1. Create the Root store (https://msdn.microsoft.com/en-us/library/h16bc8wd%28v=vs.110%29.aspx)
    2. Create the Certificate. It can be read from memory. (https://msdn.microsoft.com/en-us/library/ms148413%28v=vs.110%29.aspx)
    3. Add the certificate
    4. Close the store

    ReplyDelete