Signing executables with authenticode certificate without the need for CAPICOM.

A couple of years ago the recommended way of signing executables with an authenticode certificate was using the SignTool applications that is distributed with the Windows SDK. With the release of x64 operating systems (especially Windows 7 x64) people started seeing that tool fail since it depends on CAPICOM, and this is only available in 32-bits (and it is also not supported anymore).

I already discussed this issue in a previous blogpost. But i wanted a permanent solution because it gets annoying redirecting my co-workers to a tool that is unsupported and doesn’t work out of the box. I decided to do some research on the subject and came up with a tool that does what we need it to do, but is not a full replacement for Microsoft’s own SignTool.

I could not have done this without this exelent blog post.

I started with refactoring the p/invoke code to C#:

// #define CRYPTUI_WIZ_NO_UI     1
public const int CRYPTUI_WIZ_NO_UI = 1;

// #define CRYPTUI_WIZ_DIGITAL_SIGN_SUBJECT_FILE     0x01
public const int CRYPTUI_WIZ_DIGITAL_SIGN_SUBJECT_FILE = 1;

// #define CRYPTUI_WIZ_DIGITAL_SIGN_CERT                    0x01
public const int CRYPTUI_WIZ_DIGITAL_SIGN_CERT = 1;

// #define CRYPTUI_WIZ_DIGITAL_SIGN_COMMERCIAL  0x0001
public const int CRYPTUI_WIZ_DIGITAL_SIGN_COMMERCIAL = 1;

// typedef struct _CRYPTUI_WIZ_DIGITAL_SIGN_INFO {  
//   DWORD dwSize;  
//   DWORD dwSubjectChoice;  
//   union {    
//       LPCWSTR pwszFileName;    
//       PCCRYPTUI_WIZ_DIGITAL_SIGN_BLOB_INFO pSignBlobInfo;  
//   };  
//   DWORD dwSigningCertChoice;  
//   union {    
//       PCCERT_CONTEXT pSigningCertContext;    
//       PCCRYPTUI_WIZ_DIGITAL_SIGN_STORE_INFO pSigningCertStore;    
//       PCCRYPTUI_WIZ_DIGITAL_SIGN_CERT_PVK_INFO pSigningCertPvkInfo;  
//   };  
//   LPCWSTR pwszTimestampURL;  
//   DWORD dwAdditionalCertChoice;  
//   PCCRYPTUI_WIZ_DIGITAL_SIGN_EXTENDED_INFO pSignExtInfo;
// } CRYPTUI_WIZ_DIGITAL_SIGN_INFO;
[StructLayout(LayoutKind.Sequential)]
public struct CRYPTUI_WIZ_DIGITAL_SIGN_INFO
{
    public int dwSize;
    public int dwSubjectChoice;
    [MarshalAs(UnmanagedType.LPWStr)]
    public string pwszFileName;
    public int dwSigningCertChoice;
    public IntPtr pSigningCertContext;
    [MarshalAs(UnmanagedType.LPWStr)]
    public string pwszTimestampURL;
    public int dwAdditionalCertChoice;
    public IntPtr pSignExtInfo;
}

//typedef struct _CRYPTUI_WIZ_DIGITAL_SIGN_EXTENDED_INFO {
//  DWORD             dwSize;
//  DWORD             dwAttrFlags;
//  LPCWSTR           pwszDescription;
//  LPCWSTR           pwszMoreInfoLocation;
//  LPCSTR            pszHashAlg;
//  LPCWSTR           pwszSigningCertDisplayString;
//  HCERTSTORE        hAdditionalCertStore;
//  PCRYPT_ATTRIBUTES psAuthenticated;
//  PCRYPT_ATTRIBUTES psUnauthenticated;
//} CRYPTUI_WIZ_DIGITAL_SIGN_EXTENDED_INFO, *PCRYPTUI_WIZ_DIGITAL_SIGN_EXTENDED_INFO;
[StructLayout(LayoutKind.Sequential)]
public struct CRYPTUI_WIZ_DIGITAL_SIGN_EXTENDED_INFO
{
    public int dwSize;
    public int dwAttrFlags;
    [MarshalAs(UnmanagedType.LPWStr)]
    public string pwszDescription;
    [MarshalAs(UnmanagedType.LPWStr)]
    public string pwszMoreInfoLocation;
    [MarshalAs(UnmanagedType.LPStr)]
    public string pszHashAlg;
    [MarshalAs(UnmanagedType.LPWStr)]
    public string pwszSigningCertDisplayString;
    public IntPtr hAdditionalCertStore;
    public IntPtr psAuthenticated;
    public IntPtr psUnauthenticated;
}

// typedef struct _CRYPTUI_WIZ_DIGITAL_SIGN_CONTEXT {  
//      DWORD dwSize;  
//      DWORD cbBlob;  
//      BYTE* pbBlob;
// } CRYPTUI_WIZ_DIGITAL_SIGN_CONTEXT;
[StructLayout(LayoutKind.Sequential)]
public struct CRYPTUI_WIZ_DIGITAL_SIGN_CONTEXT
{
    public int dwSize;
    public int cbBlob;
    public IntPtr pbBlob;
}

// BOOL WINAPI CryptUIWizDigitalSign(
//      DWORD dwFlags,
//      HWND hwndParent,
//      LPCWSTR pwszWizardTitle,
//      PCCRYPTUI_WIZ_DIGITAL_SIGN_INFO pDigitalSignInfo,
//      PCCRYPTUI_WIZ_DIGITAL_SIGN_CONTEXT* ppSignContext
// );
[DllImport("Cryptui.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern bool CryptUIWizDigitalSign(
    int dwFlags,
    IntPtr hwndParent,
    [MarshalAs(UnmanagedType.LPWStr)]
    string pwszWizardTitle,
    ref CRYPTUI_WIZ_DIGITAL_SIGN_INFO pDigitalSignInfo,
    ref IntPtr ppSignContext);

// BOOL WINAPI CryptUIWizFreeDigitalSignContext(
//   PCCRYPTUI_WIZ_DIGITAL_SIGN_CONTEXT pSignContext
// );
[DllImport("Cryptui.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool CryptUIWizFreeDigitalSignContext(IntPtr pSignContext);

I added the CRYPTUI_WIZ_DIGITAL_SIGN_EXTENDED_INFO structure as well. I didn’t get this part to work, but I kept the definition for future reference.

With the P/Invoke definitions done actually signing a file was remarkably simple (don’t mind the CommandLineArguments class, it is defined in the sample download):

var cert = new X509Certificate2(cla.Certificate, cla.Password);
var digitalSignInfo = new CRYPTUI_WIZ_DIGITAL_SIGN_INFO();
digitalSignInfo.dwSize = Marshal.SizeOf(digitalSignInfo);
digitalSignInfo.dwSubjectChoice = CRYPTUI_WIZ_DIGITAL_SIGN_SUBJECT_FILE;
digitalSignInfo.pwszFileName = cla.FileToSign;
digitalSignInfo.dwSigningCertChoice = CRYPTUI_WIZ_DIGITAL_SIGN_CERT;
digitalSignInfo.pSigningCertContext = cert.Handle;
digitalSignInfo.pwszTimestampURL = cla.TimestampUrl;
digitalSignInfo.dwAdditionalCertChoice = 0;
digitalSignInfo.pSignExtInfo =IntPtr.Zero;

IntPtr pSignContext = IntPtr.Zero;
if (!CryptUIWizDigitalSign(CRYPTUI_WIZ_NO_UI, IntPtr.Zero, string.Empty, ref digitalSignInfo, ref pSignContext))
{
    throw new Win32Exception(Marshal.GetLastWin32Error(), "CryptUIWizDigitalSign");
}

if (!CryptUIWizFreeDigitalSignContext(pSignContext))
    throw new Win32Exception(Marshal.GetLastWin32Error(), "CryptUIWizFreeDigitalSignContext");

This is all there is to it. I have included a zipped sample of the application which includes a bit more plumbing (like some crude command line arguments handling). And a failed attempt to also use the extended info class (that would be needed for the original SignTool’s /d and /du options).

Download the sample here: SignToolNet

One thought on “Signing executables with authenticode certificate without the need for CAPICOM.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.