Integrated Solutions, Inc.

NAnt task for Code Signing

At ISI we like to sign our assemblies for many reasons, but chief among them is so that we know and can prove that we compiled the assembly. The vast majority of our clients purchase the source code to the applications we work on for them, thus, from time to time, we’ve had problems where a deployed app doesn’t work but the assemblies we delivered work perfectly, and later find out that some one on the client side made a “minor” code change and deployed their own assemblies ……

Thus comes in one of the great reasons for code signing ……

We are also big fans of NAnt for builds, so below is the code to our NAnt task for signing assemblies:

using System;
using System.Collections.Generic;
using System.Text;

namespace ISI.NAnt.Contrib.Tasks
{
  [global::NAnt.Core.Attributes.TaskName("ISI.SignTool")]
  public class SignTool : global::NAnt.Core.Tasks.ExternalProgramBase
  {
    [global::NAnt.Core.Attributes.BuildElement("Assemblies", Required = true)]
    public virtual global::NAnt.Core.Types.FileSet Assemblies { get; set; }

    [global::NAnt.Core.Attributes.TaskAttribute("CertificateFileName", Required = true)]
    public string CertificateFileName { get; set; }

    [global::NAnt.Core.Attributes.TaskAttribute("CertificatePasswordFileName", Required = true)]
    public string CertificatePasswordFileName { get; set; }

    [global::NAnt.Core.Attributes.TaskAttribute("TimeServerUrl", Required = false)]
    public string TimeServerUrl { get; set; }

    public SignTool()
    {
      using (Microsoft.Win32.RegistryKey regKey = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Microsoft SDKs\Windows"))
      {
       if (regKey != null)
        {
          string installRoot = regKey.GetValue("CurrentInstallFolder").ToString();
          if (!string.IsNullOrEmpty(installRoot)) base.ExeName = System.IO.Path.Combine(installRoot, @"bin\signtool.exe");
        }
      }

      TimeServerUrl = @"http://timestamp.comodoca.com/authenticode";
    }

    public override string ProgramArguments
    {
      get { return string.Empty; }
    }

    protected override void ExecuteTask()
    {
      bool doSignCode = true;

      Log(global::NAnt.Core.Level.Info, "Code Sign");

      if (string.IsNullOrEmpty(CertificateFileName) || !System.IO.File.Exists(CertificateFileName))
      {
        Log(global::NAnt.Core.Level.Error, "Missing Certificate: {0}", CertificateFileName);
        doSignCode = false;
      }

      if (string.IsNullOrEmpty(CertificatePasswordFileName) || !System.IO.File.Exists(CertificatePasswordFileName))
      {
        Log(global::NAnt.Core.Level.Error, "Missing Certificate Password: {0}", CertificatePasswordFileName);
        doSignCode = false;
      }

      if (string.IsNullOrEmpty(base.ExeName) || !System.IO.File.Exists(base.ExeName))
      {
        Log(global::NAnt.Core.Level.Error, "Missing Sign Tool: {0}", base.ExeName);
        doSignCode = false;
      }

      if (doSignCode)
      {
        string certificatePassword = null;
        using (System.IO.StreamReader streamReader = new System.IO.StreamReader(CertificatePasswordFileName)) certificatePassword = streamReader.ReadLine();

        base.Arguments.Clear();
        base.Arguments.Add(new global::NAnt.Core.Types.Argument("sign"));
        base.Arguments.Add(new global::NAnt.Core.Types.Argument(string.Format("/f \"{0}\"", CertificateFileName)));
        base.Arguments.Add(new global::NAnt.Core.Types.Argument(string.Format("/p \"{0}\"", certificatePassword)));
        base.Arguments.Add(new global::NAnt.Core.Types.Argument(string.Format("/t \"{0}\"", TimeServerUrl)));

        doSignCode = false;
        foreach (string assemblyFileName in Assemblies.FileNames)
        {
          Log(global::NAnt.Core.Level.Info, "  Signing '{0}'.", assemblyFileName);

          global::NAnt.Core.Types.Argument fileArgument = new global::NAnt.Core.Types.Argument();
          base.Arguments.Add(fileArgument);
          fileArgument.File = new System.IO.FileInfo(assemblyFileName);

          doSignCode = true;
        }

        if (doSignCode)
          base.ExecuteTask();
        else
          Log(global::NAnt.Core.Level.Error, "No Files Found");
      }
    }
  }
}

 

and add it to your build script:

    <loadtasks assembly="/path_to/ISI.NAnt.Contrib.dll" />
    <ISI.SignTool CertificateFileName="/path_to/Certificate.pfx"
                  CertificatePasswordFileName="/path_to/Certificate.pwd"
      >
      <Assemblies basedir="${publish.dir}">
        <include name="ISI.*.dll" />
        <include name="ISI.*.exe" />
      </Assemblies>
    </ISI.SignTool>

Certificate.pfx is the code signing certificate
Certificate.pwd is the plain text password for that certificate

If these files do not exist, the SignTool task just skips itself

Note, the Certificate.pfx and Certificate.pwd files only exists on our official release build system.

Published 06/19/2009 by Ron Muth