Signing files in GitHub Actions
Background
I recently wrote about the changes I had had to make to our Azure DevOps pipelines to address the changes required when code signing with a new DigiCert certificate due to new private key storage requirements for Code Signing certificates
Today I had to do the same for a GitHub Actions pipeline. The process is very similar, but there are a few differences in the syntax and the way the secrets are stored.
The Solution
Step 1: Create a Composite Action
I stored theses steps as a Composite Action for easier reuse, but you could put them within a workflow if you prefer.
The composite action installs the DigiCert tools in the first step, and then finds and signs the files in the second.
Yes, I know I could just have a single step, but I wanted to follow the Azure DevOps flow as closely as possible.
name: 'Sign Code with DigiCert'
description: 'Signs the contents of a folder with DigiCert'
inputs:
digicert-api-key:
description: 'The DigiCert API key'
required: true
tools-download-url:
description: 'The URL for the DigiCert Windows MSI'
required: false
default: https://one.digicert.com/signingmanager/api-ui/v1/releases/smtools-windows-x64.msi/download
signer_p12_file:
description: 'The DigiCert Signer P12 File as base64 encoded string'
required: true
cert_crt_file:
description: 'The DigiCert Certificate CRT File as base64 encoded string'
required: true
digicert_host:
description: 'The DigiCert Host'
required: false
default: https://clientauth.one.digicert.com
keypair_alias:
description: 'DigiCert certifiate alias'
required: true
password:
description: 'Digicert password'
required: true
file-path:
description: 'Path to scan for files'
required: true
runs:
using: "composite"
steps:
- name: Install DigiCert Client Tools
shell: pwsh
run: |
curl -X GET ${{ inputs.tools-download-url }} -H "x-api-key:${{ inputs.digicert-api-key }} " -o smtools-windows-x64.msi
msiexec /i smtools-windows-x64.msi /quiet /qn
- name: Code Sign Files
shell: pwsh
run: |
# Define the base path where signtool.exe is located
$basePath = "C:\Program Files (x86)\Windows Kits\10\bin"
# Filtering via version and architecture, could use just one of the these, depends on needs
$preferredVersion = "x64"
# Get the matching signtool path (pick the last in the list if multiple returned)
$signtoolPath = (Get-ChildItem -Path $basePath -Recurse -Filter "signtool.exe" -File | Where-Object { $_.FullName -like "*\$preferredVersion\*" })[-1] | Select-Object -ExpandProperty FullName
write-host "Found signtool at $signtoolPath"
set-content -Path 'signer.p12.base64' -Value '${{ inputs.signer_p12_file }}'
certutil -decode -f 'signer.p12.base64' 'DigiCert Signer Certificate_pkcs12.p12'
set-content -Path 'cert.crt.base64' -Value '${{ inputs.cert_crt_file }}'
certutil -decode -f 'cert.crt.base64' 'cert.crt'
# Find the files that match
write-host "Finding files that match'the path ./src/sample/bin/**/**/*.dll"
Get-ChildItem -Path "${{ inputs.file-path }}" | ForEach-Object {
$filePath = $_.FullName
write-host "Signing file $filePath"
& $signtoolPath sign /v /tr http://timestamp.digicert.com /td SHA256 /fd SHA256 /csp "DigiCert Signing Manager KSP" /kc "${{ inputs.keypair_alias}}" /f "cert.crt" $filePath
}
env:
SM_HOST: ${{ inputs.digicert_host }}
SM_API_KEY: ${{ inputs.digicert-api-key }}
SM_CLIENT_CERT_PASSWORD: ${{ inputs.password }}
SM_CLIENT_CERT_FILE: DigiCert Signer Certificate_pkcs12.p12
SM_TLS_SKIP_VERIFY: true
Step 2: Store the Certificates as Secrets
As with Azure DevOps implementation we now have a pair of files, the .CRT certificate and the .P12 signer’s certificate.
GitHub does not have a feature like Azure DevOps Secure Files. So, we have to store the certificates as secrets.
To store these certificates as GitHub Secrets you need to encode the file content into a base64 string and then add it as a secret in your GitHub repository, or Organisation or Enterprise. Use whichever level works best for you, in my case I chose to store them as Organisation secrets.
Here are the steps:
- Open Command Prompt or PowerShell.
- Use the
certutil
command to encode the file:certutil -encode yourfile.crt yourfile.crt.base64
- Open the generated
yourfile.crt.base64
file in a text editor (e.g., Notepad) and copy its content. - Add the secret to GitHub at the level you chose:
- Name your secret (e.g. CRT_FILE).
- Paste the base64-encoded content into the Value field.
Repeat this process for both the .CRT and the .P12 file.
Step 3: Store the other DigiCert settings as secrets and variables
The other DigiCert settings can be stored as a mixture of secrets and variables. Use variables if reading the value in the log is not deemed a security risk.
The secrets:
- DIGICERT_API_KEY
- DIGICERT_SIGNER_P12_FILE as base64 encoded string (see above)
- DIGICERT_CERT_CRT_FILE as base64 encoded string (see above)
- DIGICERT_CLIENT_CERT_PASSWORD
and as a variable:
- DIGICERT_KEYPAIR_ALIAS
Step 4: Using the Composite Action in a Workflow
Finally pull it all together in your workflow
- name: Sign files with Digicert
uses: blackmarble/Sign-Code-With-DigiCert@v1
with:
digicert-api-key: '${{ secrets.DIGICERT_API_KEY }}'
signer_p12_file: '${{ secrets.DIGICERT_SIGNER_P12_FILE }}'
cert_crt_file: '${{ secrets.DIGICERT_CERT_CRT_FILE }}'
keypair_alias: '${{ vars.DIGICERT_KEYPAIR_ALIAS}}'
password: '${{ secrets.DIGICERT_CLIENT_CERT_PASSWORD }}'
file-path: './src/Sample/bin/**/**/*.dll'
For the original version of this post see Richard Fennell's personal blog at Signing files in GitHub Actions