Code Signing

Tugger has support for automatically performing code signing when evaluating Starlark configuration files.

Various platforms and distribution channels enforce requirements that binaries and other artifacts are cryptographically signed by a trusted certificate.

For example:

  • On Windows, executables and installers must be signed by a trusted certificate to avoid warnings about running untrusted applications.

  • On macOS, executables, pkg installers, and more need to be signed by a trusted certificate or Gatekeeper (read: the OS) may refuse to run them.

Tugger’s support for automatic signing enables you to meet these requirements with hpoefully minimal effort.

Code Signing Support

Tugger supports signing the following signable entities:

  • PE binaries. This is the file executable format in use on Windows platforms.

  • MSI installers. This is a common file-based installer format on Windows.

  • Mach-O binaries. This is the file executable format in use on Apple platforms.

  • Apple application bundles. e.g. My Program.app directories. Bundles are a common application packaging format on Apple platforms.

Signing on Windows currently uses Microsoft’s signtool.exe to perform the signing. So signing Windows entities requires access to this tool. (We have plans to implement equivalent functionality in Rust to avoid this dependency.)

Signing Apple formats uses a pure Rust implementation of the code signing functionality and works on any machine. Apple’s codesign tool or access to Apple hardware is not required to sign Apple entities.

Code signing requires the use of a code signing certificate. See Understanding Code Signing Certificates for more.

Tugger supports using code signing certificates in the following locations:

  • From a PFX / PKCS #12 file. (e.g. .pfx or .p12 files.)

  • Certificates available in the Windows certificate store. Via the Windows certificate store, certificates stored in hardware devices (such as HSMs and hardware tokens such as YubiKeys) can also be used.

Configuring Code Signing in Starlark

Code signing needs to be explicitly enabled and configured in your Starlark configuration file.

From a high level, here’s how it works:

  1. Your Starlark configuration instantiates, configures, and enables a CodeSigner, which is the entity that performs code signing.

  2. As your configuration file is evaluated, actions that produce or encounter signable entities (such as creating Windows MSI installers) interact with registered CodeSigner instances and attempt code signing.

Tugger abstracts away a lot of the complexity around code signing, such as figuring out which files need to be signed (it looks at the content of files and determines if a file is signable). So in many cases, all you need to do is tell Tugger where your code signing certificate is and it can do the rest!

Continuing reading for details on how to customize code signing. Or just straight into Code Signing Examples.

Instantiating CodeSigner to Perform Code Signing

To perform code signing, first instantiate a CodeSigner via one of its available constructor functions:

code_signer_from_pfx_file() is the most versatile method, as it gives Tugger full access to the signing certificate and private key. However, this method is arguably the least secure, as it requires the private key to exist in a file and Tugger holds the decrypted private key in memory during signing. Both of these make the private key much more susceptible to being accessed by unwanted parties. If you are paranoid about security, you should only use this method on machines that you trust.

The code_signer_from_windows_ functions reference code signing keys stored in the Windows certificate store. Signature requests are processed through the Windows APIs and the private key never leaves the control of the Windows certificate store, helping to keep the private key secure.

Important

Constructed CodeSigner instances must be activated in order to automatically perform code signing. See Activating Automatic Code Signing for more.

Configuring CodeSigner Instances

Once you’ve obtained a CodeSigner, you may need to register additional settings to influence signing.

Registering the Issuing Certificate Chain

Produced signatures should often contain details about the chain of certificates that issued the code signing certificate. See Understanding Code Signing Certificates for more on this topic.

You may need to tell CodeSigner about the existence of these certificates.

  • When using a code signing certificate backed by the Windows certificate store, you do not need to register the certificate’s signing chain.

  • When using a code signing certificate backed by a PFX file, you need to register the certificate chain, even if those X.509 certificates are in the PFX file (we don’t yet support reading these from the PFX file). CodeSigner.chain_issuer_certificates_pem_file() is the most versatile method to register issuer certificates, as it works on all platforms and PEM is a very widespread format for storing X.509 certificates.

  • On macOS, CodeSigner.chain_issuer_certificates_macos_keychain() can be called to attempt to resolve the certificate chain by speaking directly to the macOS keychain APIs. This requires that the signing certificate be accessible in the current user’s keychain and its entire issuing chain to be present in that keychain.

Influencing Signing Operations

CodeSigner instances have the opportunity to influence individual signing operations. This gives you significant control over how signing is performed.

CodeSigner.set_signing_callback() registers a function that will be invoked on each attempted signing operation. This callback function receives an argument - a CodeSigningRequest instance - that describes the entity capable of being signed. This type exposes functionality for influencing the signing operation. For example:

See the CodeSigningRequest API documentation for all available functionality on this type.

Leveraging custom callback functions enables configuration files to employ arbitrarily complex logic for influencing code signing. Your main constraint are the settings exposed on CodeSigningRequest. If you find yourself needing a setting that doesn’t exist, please file a feature request!

Activating Automatic Code Signing

A CodeSigner needs to be activated for automatic use by Tugger. i.e. your signable files won’t be signed as your Starlark configuration file is evaluated unless a CodeSigner is activated.

To activate your CodeSigner, simply call CodeSigner.activate().

Code Signing Actions

Various activities within the evaluation of your Starlark configuration file trigger the assessment of - and possible performing of - code signing.

Each unique activity has its own string action name describing it. This name is accessible via CodeSigningRequest.action, enabling callback functions to key off of it. For example, you may want to not sign during certain operations.

The following named actions are defined by Tugger:

file-manifest-install

Used when a FileManifest is materialized on the filesystem through an action like FileManifest.install().

macos-application-bundle-creation

When a macOS Application Bundle is created by Tugger.

This will be triggered by MacOsApplicationBundleBuilder.build().

windows-installer-creation

When a Windows installer file is created by Tugger.

Methods like WiXMSIBuilder.build() and WiXBundleBuilder.build() will trigger this action.

windows-installer-file-added

When a file that will be installed is added to a Windows installer.

Triggered by WiXMSIBuilder.add_program_files_manifest(), WiXInstaller.add_install_file(), and WiXInstaller.add_install_files().

Other applications extending Tugger’s core functionality may define their own actions.

Duplicate Events

It is possible for the same logical file to trigger multiple signing events as it is processed. For example, MacOsApplicationBundleBuilder.build() may trigger an event for macOS Application Bundle generation then a later action loads the bundle files into a FileManifest and materializes them somewhere else via FileManifest.install(), which would trigger an additional signability check.

As a result, the same file or entity may be signed multiple times.

If this behavior is undesirable, the use of a custom callback function can be used to choose which signing requests to respond to.

Unfortunately, we do not yet expose metadata on CodeSigningRequest indicating if a file is signed or not. This would likely be the obvious attribute to filter against. This feature is tracked at https://github.com/indygreg/PyOxidizer/issues/400.

Code Signing Examples

Automatically Sign all Signable Content with a Specific Certificate in the Windows Store

Say you have a code signing certificate in the Windows certificate store with the SHA-1 thumbprint deadbeefdeadbeefdeadbeefdeadbeefdeadbeef and you want Tugger to sign all signable files as it runs. Here’s what you’ll need to do in your Starlark configuration file:

signer = code_signer_from_windows_store_sha1_thumbprint("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef")
signer.activate()

As Tugger encounters .exe, .dll, .msi files and any file that it identifies as signable, it will attempt to automatically sign them!

Choosing a Code Signing Certificate Dynamically

Say you have multiple code signing certificates but want to parameterize which one to use. We can do that through the use of the VARS global dict, which holds settings passed in via the command line.

PFX_PATH = VARS.get("PFX_PATH")
PFX_PASSWORD = VARS.get("PFX_PASSWORD", "")

# This needs to be in its own function because Starlark doesn't allow `if`
# at the file/module scope.
def make_code_signers():
    if PFX_PATH:
        signer = code_signer_from_pfx_file(PFX_PATH, PFX_PASSWORD)
        signer.activate()


# Don't forget to call the function!
make_code_signers()

Then when running the configuration file, specify an extra variable. e.g.:

$ pyoxidizer --var PFX_PATH /path/to/certificate.pfx --var PFX_PASSWORD hunter2

Or you could use functions like prompt_confirm(), prompt_input(), and prompt_password() to ask the user which certificate to use.

def make_code_signers():
    if prompt_confirm("enable code signing?", default=False):
        pfx_path = prompt_input("enter path to PFX file:")
        pfx_password = prompt_password("enter path to PFX password:", confirm=True)

        signer = code_signer_from_pfx_file(pfx_path, pfx_password)
        signer.activate()

make_code_signers()

Selectively Ignoring Files to Sign

It is common to want to ignore certain files from signing. For example, you may ship a pre-built binary that already has a valid code signature. Here’s how you can do that.

# Define a function that will be called for every signing request that
# can influence operation.
def code_signer_callback(request):
    # Match a known filename that doesn't need signed and set
    # `prevent_signing = True` to prevent it from being signed.
    if request.filename == "vcruntime140.dll":
        request.prevent_signing = True


signer = code_signer_from_windows_store_sha1_thumbprint("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef")
signer.set_signing_callback(code_signer_callback)
signer.activate()

You could even use the prompt_confirm() function to prompt whether to sign each file:

def code_signer_callback(request):
    request.prevent_signing = not prompt_confirm("sign %s?" % request.filename)

signer = code_signer_from_...()
signer.set_signing_callback(code_signer_callback)
signer.activate()

Understanding Code Signing Certificates

A code signing certificate consists of a secure, private key and a public certificate that describes itself to others. These components are strictly separate but are often represented and stored together.

The public certificate is an X.509 certificate, much like those used in HTTP to identify web sites. The main difference is that the certificate’s subject describes a person or organization (instead of a website) and the certificate contains attributes that denote it for use by code signing.

Like web site X.509 certificates, code signing certificates are signed by another X.509 certificate. This is called the issuing certificate. There is often a chain of certificates - the certificate chain - leading to a self-signed certificate (a certificate whose issuer was itself), which is referred to as the root certificate.

Typically, the certificate chain is included in code signatures. This enables readers of the signature to have full access to all relevant certificates, without an implicit dependency on them being present on the reading machine. This enables validation to be conducted more robustly.

Code Signing Certificate Storage

Code signing certificates can be stored in a number of formats. Here are the popular ones:

  • As standalone .pfx or .p12 files. These are files containing data as defined by the PFX and PKCS #12 specifications. Most tools that support saving code signing certificates to files support this format if not use it by default.

  • In your operating system’s certificate store. Windows, macOS, and other operating systems have built-in functionality for storing and accessing certificates. On Windows, the certmgr.msc tool can be used to view certificates. On macOS, Keychain Access is the official GUI application.

In addition, the public X.509 certificates and the certificates in the certificate chain are often represented as PEM. This is a human-readable text format with content like -----BEGIN CERTIFICATE-----. PEM is actually base64 encoded BER/DER encoding of ASN.1 data structures, but that’s not important. What is important is public certificates are often stored in files having this -----BEGIN CERTIFICATE----- content. These files often have the extension .pem or .crt.

The certificate chain is constant for the lifetime of a code signing certificate. So it is possible to export these certificates to a persisted file and reference this file when you need to access the issuer certificates chain.

Securing Your Code Signing Certificate

Your code signing certificate’s private key attests that its owner was in possession of that certificate and has vouched for the integrity of whatever it signed.

Important

Code signing certificates can be very attractive theft targets for hackers, as possession of a code signing certificate enables you to sign software that can run on other machines and appears to be trusted. Therefore, it is often important to try to secure your code signing certificates!

The most secure way to store code signing certificates is in dedicated hardware devices, such as HSMs or personal hardware tokens (such as YubiKeys). Often, the private key component of the certificate is generated directly in said hardware and it is impossible to export the private key and obtain its raw value. Instead, operations like signing are issued to the hardware and the hardware gives you the rest.

Tugger doesn’t yet support interfacing directly with hardware devices. However, we do have support for interfacing with the operating system’s certificate stores:

  • On Windows, a certificate in the Windows certificate store can be referenced by its SHA-1 fingerprint. (This is the preferred mechanism to reference a certificate on Windows.)

  • On Windows, a certificate in the Windows certificate store can be referenced by specifying a string to match against in the certificate’s subject field. (This is less precise than specifying a certificate’s SHA-1 fingerprint.)

  • On Windows, you can tell the signing tool to automatically find the most appropriate certificate to use. It will look for a certificate in known certificate stores. (This is the least precise of all options available on Windows.)

Note

Your operating system’s certificate store can often interface with hardware devices holding code signing certificates. So Tugger’s support for interfacing with the operating system store is often just as effective as interfacing directly with hardware devices.

For example, on Windows, certificates stored in a YubiKey will be available if you have the YubiKey Smart Card Minidriver installed.

If Tugger doesn’t support using a remote certificate, you will need to export a certificate to a file and have Tugger use that. If you export your certificate to a file, you should take care to secure that file as best you can.

File-based code signing certificates often exist in .pfx or .p12 files. These are often protected with a password. You should use a strong and unique password to secure this file.

Important

If someone else gains access to the file containing your code signing certificate, they will be able to perform an offline attack using as many compute resources as possible to guess your password and gain access to the code signing certificate.

You should take the following precautions to protect file-based code signing certificates:

  • Choose a strong, unique password for protecting the file content.

  • Limit the time the files exist. If you can create the file only when needed, this is better than having the file linger on the filesystem.

  • Limit the number of copies of the file. Every copy of the file is an opportunity for the file to be obtained by someone else.

Exporting a Code Signing Certificate from macOS Keychain

Apple platforms require a code signing certificate issued by Apple to sign distributed files.

If you have an Apple-issued code signing certificate, it is likely registered in a keychain on your machine. Tugger doesn’t currently support interfacing directly with the macOS keychain and you will need to export your signing certificate to a PFX / .p12 file so Tugger can use it. Here’s how to do that.

  1. Press command + spacebar and search for and open the Keychain Access application.

  2. Make sure the correct keychain is selected. The keychain code signing certificates are typically located in is the login keychain under the Default Keychains list.

  3. From the horizontal list of filters above the main pane, select Certificates (it is probably the last item).

  4. Find the certificate you want to export. It likely has a name like Developer ID Application: <your name (some ID)>

  5. Do a double finger tap, right click, or File -> Export Items ... to bring up the export dialog.

  6. For the file format, make sure Personal Information Exchange (.p12) is selected.

  7. Navigate to a folder where you want to save the file, choose an appropriate name, and click Save.

  8. You will be asked for a password which will be used to protect the exported items. Enter one. This password will need to be provided to Tugger later to unlock the content in the file.

  9. You may be prompted to enter the password to the keychain to allow the key export. If so, enter that password.

  10. You may be prompted multiple times. Just keep entering your keychain password(s) until it is done.

  11. You are done! There should be a .p12 file wherever you told Keychain Access to save it.

Important

Please see Securing Your Code Signing Certificate for important information on keeping your file-based code signing certificate secure.

Finding the Code Signing SHA-1 Thumbprint on Windows

On Windows, it is recommended to use code signing certificates in the Windows certificate store and to specify those certificates via their SHA-1 thumbprint, which should uniquely identify a certificate.

The Windows certificate store supports interfacing with hardware certificate stores (such as YubiKeys and other hardware devices). So this method should work with connected hardware certificate stores as well.

  1. Press Windows Key + r to open the Run panel. Type in certmgr.msc and run that program.

  2. Code signing certificates are likely under Personal -> Certificates. Find that item in the tree and look for a certificate in the main pane.

  3. Find the certificate you want to use and double click on it to view its details.

  4. Open the Details tab.

  5. In the table of fields, find and select Thumbprint.

  6. Copy the 40 character hexadecimal value that is printed.

The SHA-1 thumbprint can be fed into code_signer_from_windows_store_sha1_thumbprint() to construct a CodeSigner that uses the specified certificate.

If the certificate is protected by a password or requires key to unlock, you should see prompts to do that as Tugger attempts to sign things.

Exporting a Code Signing Certificate from Windows Certificate Store

Code signing certificates on Windows are often stored in the Windows certificate store.

Important

Tugger has support for using certificates directly in the Windows certificate store. Exporting certificates to files will likely result in a net loss of security.

Here is how you can export a certificate to a PFX file.

  1. Press Windows Key + r to open the Run panel. Type in certmgr.msc and run that program.

  2. Code signing certificates are likely under Personal -> Certificates. Find that item in the tree and look for a certificate in the main pane.

  3. Double click on the certificate you want to export, open its Details table, and click the Copy to File... button. This should open the Certificate Export Wizard.

  4. Click Next.

  5. Make sure Yes, export the private key is selected and click Next.

  6. For the format, make sure the selected value is Personal Information Exchange PKCS #12 (PFX). For the checkboxes, check Include all certificates in the certificate path, if possible. Then click Next.

  7. You should be prompted for a password. Enter a secure, unique password. In the Encryption drop-down, ensure TripleDES-SHA1 is selected (we don’t yet support AES256-SHA256). Then click Next.

  8. Select a filename and click Next.

  9. Click Finish to close the wizard.

Important

Please see Securing Your Code Signing Certificate for important information on keeping your file-based code signing certificate secure.