So we started work on the Jira installer for Windows this week. Amongst the heap of tasks involved in this activity, there was one little task labeled ‘Get code-signing certificate to stop windows warning about security of unknown publisher’ with an estimate of 1h. A little ambitious I thought at first. Then again we already have a Java applet that’s been code-signed, so it should just be a matter of taking its certificate and applying it to the installer exe right? In practice it took me about 6h to implement.
Background
Here’s a little background about how the whole code signing mechanism works (this was all new to me).
To get a code-signing certificate, you firstly generate a public/private key pair using Java’s keytool. The public key/certificate is then submitted to a key signing authority (Thawte in our case) who will verify that we really are Atlassian, and sign our public certificate back. (Generally they will send back a certificate chain, with the Thawte public CA at the top and our Atlassian certificate at the bottom. This was then imported into our keystore that was used to generate the public/private pair in the first place (using keytool). So, the keystore stores our private key, public certificate as well as the whole certificate chain from Thawte.
When signing jar files (such as for the screenshot applet), we simple pass the whole keystore to the signjar ant task and it knows how to extract all the right information from the keystore to sign the jar. Unfortunately things aren’t as simple for windows.
Implementation
In windows, you sign executables with a utility called signcode. There’s a mono version for this and it’s also included in install4j (with a slight modification that will allow you to specify a password for your private key…very useful). We build Jira on unix and the installer therefore also needs to build on unix.
Here’s what needed to be done to get mono up:
1. sudo apt-get install mono
2. sudo apt-get install mono-mcs (contains the signcode utilities and cert2spc)
Signcode takes a couple of arguments:
1. An SPC file (Software Platform Certificates) which is basically a windows format of your public certificate.
2. Your private key (in PVK format), also a windows format. The documentation of the tool forgets to mention this…
3. Password for the private key
4. A location of a timestamp server, such as http://timestamp.verisign.com/scripts/timstamp.dll
5. The executable you want to sign.
The get the SPC file I had to export our certificate from the keystore first using:

keytool -exportcert -keystore atlassian.keystore -alias atlassian -file atlassian.crt

I then converted this to an spc file using:

cert2spc atlassian.crt atlassian.spc

Now for the PVK file. keytool does not provide a way to extract the private keys from the keystore. A custom bit of Java code was needed and after a bit of googling I found just what I needed (this actually has a few more bits in here to also extract the entire certificate chain from the keystore…more about this later):

// How to export the private key from keystore?
// Does keytool not have an option to do so?
// This example use the "testkeys" file that comes with JSSE 1.0.3
import sun.misc.BASE64Encoder;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.security.*;
import java.security.cert.Certificate;
class ExportPriv
{
public static void main(String args[]) throws Exception
{
ExportPriv myep = new ExportPriv();
myep.doit();
}
public void doit() throws Exception
{
KeyStore ks = KeyStore.getInstance("JKS");
String fileName = "/path/to/your/keystore/your.keystore";
char[] passPhrase = "password".toCharArray();
BASE64Encoder myB64 = new BASE64Encoder();
File certificateFile = new File(fileName);
ks.load(new FileInputStream(certificateFile), passPhrase);
KeyPair kp = getPrivateKey(ks, "password", passPhrase);
Certificate[] certificateChain = ks.getCertificateChain("alias");
for (int i = 0 ; i < certificateChain.length; i++)
{
File output = new File("/tmp/cert"+i+".crt");
FileOutputStream out = new FileOutputStream(output);
out.write(certificateChain[i].getEncoded());
out.flush();
out.close();
}
PrivateKey privKey = kp.getPrivate();
String b64 = myB64.encode(privKey.getEncoded());
File output = new File("/tmp/private.key");
FileOutputStream out = new FileOutputStream(output);
out.write(privKey.getEncoded());
out.flush();
out.close();
System.out.println("-----BEGIN PRIVATE KEY-----");
System.out.println(b64);
System.out.println("-----END PRIVATE KEY-----");
}
// From http://javaalmanac.com/egs/java.security/GetKeyFromKs.html
public KeyPair getPrivateKey(KeyStore keystore, String alias, char[] password)
{
try
{
// Get private key
Key key = keystore.getKey(alias, password);
if (key instanceof PrivateKey)
{
// Get certificate of public key
Certificate cert = keystore.getCertificate(alias);
// Get public key
PublicKey publicKey = cert.getPublicKey();
// Return a key pair
return new KeyPair(publicKey, (PrivateKey) key);
}
} catch (UnrecoverableKeyException e)
{
} catch (NoSuchAlgorithmException e)
{
} catch (KeyStoreException e)
{
}
return null;
}
}

Note: This code is originally copied from this forum entry with some of my modifications added on.
Great so now I had the private key. What I didn’t know was that it was in the wrong format and the signcode utility doesn’t provide any feedback about this. After some more googling I found the PVK format and a little pvk tool for windows that did the conversion. Signcode now worked!

mono /home/andreask/products/java/install4j/resource/signcode.exe -spc atlassian.spc -v private.pvk -vp password -t http://timestamp.verisign.com/scripts/timstamp.dll JIRA_Enterprise_3_8-DEV_windows.exe

Except for the fact that the signed file still wasn’t accepted by Windows. It complained that it couldn’t find a trusted root for the Atlassian certificate. At about 17:30 PM after stuffing around with code signing stuff I simply couldn’t realize what was obvious. I had to export the entire certificate chain from the keystore (hence the bit of java code to write out cert0.crt, cert1.crt, etc files above) and use this to create the SPC file:

cert2spc cert0.crt cert1.crt cert2.crt atlassian.spc

Results
When running the installer from a network drive (or presumbably straight from the internet) you now get a little security message mentioning Atlassian as the publisher of the file.
When clicking on Atlassian, the certificate information is displayed correctly.

Fresh ideas, announcements, and inspiration for your team, delivered weekly.

Subscribe now