A Developer’s guide to Code Signing Certificates
Tags: security, devops, codenewbie
Published: 2020-04-30 01:50:20

Code Signing Certificates are great! They allow a developer to digitally sign their binaries to insure they haven’t been modified before being installed. Not so great is the way Code Signing Certificates get implemented in many organizations. It often starts with an Ops initiative, followed by Devs. The folk impacted the most, are kept out of the loop until a decision has been made. It’s never fun to have something foisted on your workflow. There is  no denying it however– some lead time is required to integrate code signing certificates into a workflow (even though most IDEs make it easier than ever). But what if you’re expected to suddenly use and intimately understand code signing certificates in addition to everything else you’re trying to get done this sprint? (If you’re submitting an app to Apple’s store, digital signing is a requirement, for example). Hopefully this guide is enough to get you started.

First off, what does a code signing certificate even do? Much like any SSL/TLS certificate, a code signing certificate consists of a private key (known only to the build machine), and a public key, signed by a trusted third party (think Thawte or Comodo). The build machine takes a hash of the binary and then signs that hash, tacking it on to the file in a way which can be read by a client who, in turn,  takes their own hash and compares it against the certificate. Visual Studio, Eclipse, and pretty much any other IDE you can think of will have documentation on how to add a code signing certificate to your project. While a code signing certificate does bestow some pretty significant security gains, much of the time the impetus for implementing this technology is so that users aren’t prompted with a message about the application being potentially unsafe.

Issues and Extras
Unfortunately, while simple in practice, code signing certificates do have a couple of key “gotchas”. Firstly, unlike a regular certificate, it doesn’t make sense for a code-signing certificate to have a hard and fast expiration date. While in certain circumstances it might be valuable to have an application stop working after a certain date, most of the time this is unacceptable. This is where time-stamping servers come in. When you specify a time stamping server (usually hosted by any code signing certificate issuer), client’s compare the expiration against the date the code was signed, instead of the current date. Make sure that when you sign your binaries that you specify a time stamping server. Otherwise, your users could be in for a rude surprise.

The other major gotcha with Code Signing Certificates is market fragmentation. Microsoft uses the Authenticode standard (https://docs.microsoft.com/en-us/windows-hardware/drivers/install/authenticode), and certificates are requested via the SignTool utility. Microsoft only accepts extended validation certificates (required for Windows Drivers for example) from Symantec, Certum, Entrust, GlobalSign, Sectigo, and Digicert.  Apple actually issues their own code signing certificates called “Distribution Certificates” and ties code to an Apple Developer Account. Java applications have yet a different set of requirements and are signed using the keytool and jarsigner utilities bundled with your JDK. Java maintains its own listed of trusted CAs which is dependent upon your JRE distribution. Code must be signed by a trusted CA, but if your code runs on unmanaged clients, it is often wise to choose a widely trusted CA such as Digicert or Thawte for maximum compatibility.

Often, an organization responds to this fragmentation by purchasing a couple of different types of code signing certificates in case one has to be quickly swapped for another and code redeployed. Certificate Resellers  are a good choice for this. They offer multiple code-signing certificates from different vendors through a single pane of glass. See: https://www.ssltrust.com/ssl-certificates/code-signing.

One surprisingly useful strategy is to leverage code-signing certificates in order to avoid human error when deploying code to dev and test environments. Test environments, unlike production ones, are typically only accessed by managed endpoints where the root certificate store is under your organization’s control. Simply by issuing a different internal CA per environment, you can prevent accidentally deploying the wrong code (n.b. you’ll have to make sure your CI/CD tool doesn’t continue with an install even if the installer is unsigned).

Another thing to consider is how “deep” you want your code signing to go. Most environments will only require that the installer is digitally signed, but there is value in digitally signing ALL executables, not just the installer binary. This will help prevent heuristic-based virus scanners from flagging your code as malware in unmanaged environments.

All in all, make sure to consider the certificate lifecycle for each kind of code that you’re trying to deploy, and consider making a business case for purchasing an HSM in order to store the private keys required for signing code. If you keep the above in mind, switching from unsigned code to automatically signed code is a breeze.