Code Signing & Packaging Windows Apps on a Mac

I've been a cross-platform developer for many years.  Lately my development tool of choice is Unity, which makes it trivial to build Mac, Windows, and Linux apps right on my Mac.

However, there is an important step that Unity doesn't do for you: code-signing your built apps.  If your apps aren't signed, then recent versions of Windows (or Mac OS X, for that matter) will throw up scary warnings and make your users jump through extra hoops to run them.



 

Most people use the standard Windows development tools to sign their apps.  After all, you do need to test under Windows now and then to make sure your app is working properly anyway.  However, if you're already doing your builds on a Mac, it sure would be convenient to code-sign and package them there as well.

It turns out that this is quite doable, thanks to an open-sounce tool called osslsigncode.  And, with a bit more effort, you can even make a nice Windows installer too, all on your Mac!  Here's how to get set up.

CODE SIGNING

  1. Downloaded osslsigncode-1.7.1.tar.gz from: http://sourceforge.net/projects/osslsigncode/

  2. Now we need to build it. This is the usual configure/make dance, but for me, it didn't work right away, because the "autoheader" command was not found. So:
    • Follow this blog post to install the missing tools into ~/devtools, just as he did.
    • Now build osslsigncode (using the same 'build' environment variable we set up as part of the previous step):
              cd osslsigncode-1.7.1
      	./configure --prefix=$build
      	make             (worked this time!)
      	make install    (installed into ~/devtools/bin)

  3. Edit your .profile to include ~/devtools/bin in your PATH. Add this line to the bottom of the file:
    	export PATH=$PATH:/Users/yourUserName/devtools/bin/

  4. Verify that this all worked by entering "osslsigncode -v" in Terminal. You should get the osslsigncode version and usage summary.

  5. Now you need to get a Windows developer certificate. Unfortunately you can't use your Apple certificate (well, actually you can, but it doesn't help because Apple isn't a certificate authority that Windows recognizes). Here is a list of root certificate authorities recognized by Windows. You need to get a certificate from one of these guys, or from a reseller of one of these guys. I went with TheSSLStore.com, which sells Comodo certificates for $85/year . Plan to spend a few days of back-and-forth convincing Comodo that you are a real person/company.

  6. When I finally got my certificate, Firefox automatically "installed" it in its own data files. Go to Preferences > Advanced, select the Certificates tab, and click View Certificates. Now, under Your Certificates, you should find your freshly purchased certificate (and this is also where you can look in the future to see when it expires).  Select it, and use the "Backup..." button to save it to a .p12 file, which contains both your certificate and your private key.

  7. osslsigncode can't use a .p12 file directly; it needs a separate .pem file for the certificate and for the private key. You can convert these using the built-in openssl command in the Terminal:
    	openssl pkcs12 -in Windows-Dev-Cert.p12 -out win-dev.crt.pem -clcerts -nokeys
    	openssl pkcs12 -in Windows-Dev-Cert.p12 -out win-dev.key.pem -nocerts -nodes
    Now the first output file (win-dev.crt.pem) contains your certificate, and the second one (win-dev.key.pem) contains your private key, both in PEM format. This is a plain-text format, so feel free to poke inside and verify that each one is what you expect.

So now you're all set: you have your Windows developer certificate and private key on your Mac, as well as the osslsigncode command to do the magic.  Whenever you do a build, you can now code-sign it with a shell command like this:

osslsigncode sign -certs win-dev.crt.pem -key win-dev.key.pem -n "YourAppName" -i http://yourcompany.com/ -in YourApp.exe -out YourApp-Signed.exe

This produces a new executable, properly signed.  Windows may still grumpily complain that it's not a well-known app, but this is a much less dire warning, and easier for the user to ignore.

PACKAGING

On the Mac, a simple zipped application is perfectly fine, but on Windows, users are trained to expect an installer.  Indeed, if you just send a zip file, Windows Explorer will show the contents of that without actually unzipping it, and many users will attempt to run your app from within the zip file — which doesn't work.  So, you really need an installer on Windows.

I spent a while looking for cross-platform installer makers that would let us do a trick similar to what osslsigncode does for code signing.  But I had no luck.  NSIS claims to be able to do this, but its documentation is very poor (especially for Mac), and I just couldn't get the darn thing to build.

So in the end I went with Inno Setup, which is a free, common, and well-regarded installer maker on Windows.  Unfortunately, it only runs on Windows itself.  What  to do?

Wine to the rescue!  Though I don't have a lot of experience with Wine, this blog post encouraged me to give it a try.  It didn't actually explain how to get it all set up, though.

Wine has no official binaries for Mac, and recommends using a package manager (e.g. Homebrew).  Yuck.  Those package managers drive me nuts because they spew files all over my file system, and they are not generally compatible with each other.  If you're already in bed with one, then by all means go ahead and use it; but I prefer neat, self-contained applications.  It turns out there is exactly that: a third-party Wine wrapper called Wineskin.

To start, download & run Wineskin.  Select an engine and wrapper version (I just used the latest of each).  Hit "Create New Blank Wrapper", and when prompted for a name, enter "InnoSetup".  Let it chug a while.

When it's done, it will have created an Applications folder inside your home directory, and then a Wineskin folder inside that.  Double-click the InnoSetup wrapper there (i.e. in  ~/Applications/Wineskin).  The first time I tried this, it didn't work; but I bravely double-clicked it again, and this time it launched. Click the "Install" button, and select the isetup-5.5.6.exe file downloaded from the INNO Setup site.

It will prompt you were to install; accept the default location of C:\Program Files\Inno Setup 5.  Choose to include the Inno Setup Preproccesor (ISPP).

Presto!  You're now running Inno Setup on your Mac.  That "C:" drive maps to a file inside the Inno Setup bundle, but what's really cool is that even in the Windows (Wine) application, in any file dialog, you can navigate out of that to anywhere on your Mac hard drive.  So you can work with your files wherever they live.

Here a decent tutorial for using Inno Setup for a Unity app.

I followed that guide and quickly had an installer for my game.  Not only that, but the details of what the installer does are all defined by a very readable script (.iss file) that you could easily tweak for each new version, or for the demo build.

PUTTING IT ALL TOGETHER

I hate manual build steps.  We run High Frontier on an incremental-release model, but even so, we only do a release every three or four weeks.  So if there are too many manual steps — even when well documented — I'm too likely to screw something up.  And our build process for Windows has gotten fairly complex by this point; we need to (1) build in Unity, (2) code-sign the executable, (3) run Inno Setup to make an installer, and then (4) code-sign the installer.

I haven't yet figured out a way to fully automate the installer, but since everything it does is controlled by that .iss file, it's very easy and repeatable.  I did, however, make scripts for the rest of it.  To avoid name clashes while code-signing, I set up "signed" and "unsigned" subdirectories in my build folder.  In fact the whole setup looks like this:



So my Windows build process now amounts to this:

  1. Do a build in Unity, saving to the Win/unsigned folder.

  2. Run the package-win script, which contains:
    #!/bin/bash
    
    cd /Users/youruserid/yaddayadda/Build/Win
    rm -rf signed/*
    
    echo 'Code-signing Windows build...'
    osslsigncode sign -certs ../win-dev.crt.pem -key ../win-dev.key.pem -n "App Name" -i http://yourcompany.com/ -in unsigned/AppName.exe -out signed/AppName.exe
    cp -pR unsigned/AppName_Data signed/
    
    rm signed/*Setup.exe
    
    echo
    echo 'NOW: RUN INNOSETUP!'
    echo 'InnoSetup should create installer AppNameSetup.exe.'
    echo 'After that is done, run the package-win-step2 script.'
    echo

  3. Now, as the script reminded me to do, I run Inno Setup, with that .iss file telling it what to do. (It's configured to write to the Win/unsigned folder, since the installer is not yet signed.)

  4. Then I run the second script, package-win-step2:
    #!/bin/bash
    
    cd /Users/youruserid/yaddayadda/Build/Win
    
    echo 'Code-signing Windows installer...'
    osslsigncode sign -certs ../win-dev.crt.pem -key ../win-dev.key.pem -n "App Name Setup" -i http://yourcompany.com/ -in unsigned/AppNameSetup.exe -out signed/AppNameSetup.exe
    
    echo 'Installing Windows builds in web hierarchy...'
    mv -fv signed/AppSetup.exe ../yourcompany.com/download/
    That last step of the script just moves the signed installer to wherever it should go (I have a local copy of my web files, which I push up to the server with rsync when everything's ready).

So that's it!  I'm able to build a properly signed Windows installer, containing a properly signed executable, all from the comfort of my Mac.

I hope this post helps other Mac-using Windows developers out there.  If you have any other tips and tricks for those of us in this boat, please leave a comment below!