tugger/docs/tugger_code_signing.rst
.. py:currentmodule:: starlark_tugger
.. _tugger_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:
Tugger's support for automatic signing enables you to meet these requirements with hpoefully minimal effort.
Tugger supports signing the following signable entities:
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
:ref:tugger_code_signing_certificates for more.
Tugger supports using code signing certificates in the following locations:
.pfx or .p12 files.)Code signing needs to be explicitly enabled and configured in your Starlark configuration file.
From a high level, here's how it works:
CodeSigner, which is the entity that performs code
signing.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 :ref:tugger_code_signing_examples.
CodeSigner to Perform Code SigningTo perform code signing, first instantiate a :py:class:CodeSigner via one
of its available constructor functions:
code_signer_from_pfx_filecode_signer_from_windows_store_sha1_thumbprintcode_signer_from_windows_store_subjectcode_signer_from_windows_store_auto:py:func: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 :py:class:CodeSigner instances must be activated in order
to automatically perform code signing. See :ref:tugger_code_signing_activation
for more.
CodeSigner InstancesOnce you've obtained a :py:class: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
:ref:tugger_code_signing_certificates for more on this topic.
You may need to tell :py:class:CodeSigner about the existence of
these certificates.
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.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 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
:py:class:CodeSigner instances have the opportunity to influence individual
signing operations. This gives you significant control over how signing is
performed.
:py:meth:CodeSigner.set_signing_callback registers a function that will be
invoked on each attempted signing operation. This callback function receives
an argument - a :py:class:CodeSigningRequest instance - that describes
the entity capable of being signed. This type exposes functionality
for influencing the signing operation. For example:
CodeSigningRequest.defer to True will opt this
:py:class:CodeSigner out of signing this particular entity.CodeSigningRequest.prevent_signing to True will
prevent this and other :py:class:CodeSigner from signing this entity.See the :py:class: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 :py:class:CodeSigningRequest. If you find
yourself needing a setting that doesn't exist, please file a feature request!
.. _tugger_code_signing_activation:
A :py:class: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 :py:class:CodeSigner is
activated.
To activate your :py:class:CodeSigner, simply call
:py:meth:CodeSigner.activate.
.. _tugger_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 :py:attr: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 :py:class:FileManifest is materialized on the filesystem
through an action like :py:meth:FileManifest.install().
macos-application-bundle-creation
When a macOS Application Bundle is created by Tugger.
This will be triggered by :py:meth:MacOsApplicationBundleBuilder.build().
windows-installer-creation
When a Windows installer file is created by Tugger.
Methods like :py:meth:WiXMSIBuilder.build and
:py:meth: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 :py:meth:WiXMSIBuilder.add_program_files_manifest,
:py:meth:WiXInstaller.add_install_file, and
:py:meth:WiXInstaller.add_install_files.
Other applications extending Tugger's core functionality may define their own actions.
.. _tugger_code_signing_duplicate_events:
It is possible for the same logical file to trigger multiple signing events
as it is processed. For example, :py:meth:MacOsApplicationBundleBuilder.build()
may trigger an event for macOS Application Bundle generation then a later
action loads the bundle files into a :py:class:FileManifest and materializes
them somewhere else via :py:meth: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 :py:class: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.
.. _tugger_code_signing_examples:
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:
.. code-block:: python
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!
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.
.. code-block:: python
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 :py:func:prompt_confirm, :py:func:prompt_input,
and :py:func:prompt_password to ask the user which certificate to use.
.. code-block:: python
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()
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.
.. code-block:: python
# 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 :py:func:prompt_confirm function to prompt whether
to sign each file:
.. code-block:: python
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()
.. _tugger_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.
.. _tugger_code_sigining_certificate_storage:
Code signing certificates can be stored in a number of formats. Here are the popular ones:
.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.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.
.. _tugger_securing_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:
.. 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 <https://www.yubico.com/support/download/smart-card-drivers-tools/>_
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:
.. _tugger_code_signing_apple:
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.
command + spacebar and search for and open the Keychain Access
application.login keychain under the
Default Keychains list.Certificates (it is probably the last item).Developer ID Application: <your name (some ID)>File -> Export Items ... to
bring up the export dialog.Personal Information Exchange (.p12)
is selected.Save..p12 file wherever you told Keychain Access to save it... important::
Please see :ref:tugger_securing_code_signing_certificate for important
information on keeping your file-based code signing certificate secure.
.. _tugger_code_signing_windows_thumbprint:
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.
Windows Key + r to open the Run panel. Type in
certmgr.msc and run that program.Personal -> Certificates.
Find that item in the tree and look for a certificate in the main pane.Details tab.Thumbprint.The SHA-1 thumbprint can be fed into
:py:func:code_signer_from_windows_store_sha1_thumbprint to construct a
:py:class: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.
.. _tugger_code_signing_windows_export:
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.
Windows Key + r to open the Run panel. Type in
certmgr.msc and run that program.Personal -> Certificates.
Find that item in the tree and look for a certificate in the main pane.Details
table, and click the Copy to File... button. This should open the
Certificate Export Wizard.Next.Yes, export the private key is selected and click Next.Personal Information Exchange PKCS #12 (PFX). For the checkboxes, check Include all certificates in the certificate path, if possible. Then click Next.Encryption drop-down, ensure TripleDES-SHA1 is selected
(we don't yet support AES256-SHA256). Then click Next.Next.Finish to close the wizard... important::
Please see :ref:tugger_securing_code_signing_certificate for important
information on keeping your file-based code signing certificate secure.