CI-Ready macOS Signing: Combining Apple Distribution & Installer Certificates for GitHub Actions
In my previous blog post, Exporting Apple Distribution Certificates for CI/CD — The Right Way, I shared a reliable method to extract and repackage Apple Distribution certificates into a clean .p12
file for use in GitHub Actions — avoiding legacy encryption pitfalls and ensuring compatibility across CI environments.
That approach worked perfectly for iOS signing, but when I tried to create a pipeline that supports macOS Catalyst apps, I ran into another requirement I forgot: Apple mandates an additional certificate — the 3rd Party Mac Developer Installer certificate — for signing the installer package used in notarization or App Store submission.
The problem? Importing these two certificates separately into GitHub Actions failed — not because of the signing itself, but because of how the certificate import action manages keychains. In this post, I’ll show how combining both certificates into a single .p12
file was not only necessary but the only way to make the macOS signing process fully work in CI. I’ll also walk through the exact OpenSSL steps that did the trick.
Why You Need Two Certificates for macOS Distribution
To successfully sign and distribute a macOS app via TestFlight or the Mac App Store, you need:
- Apple Distribution certificate – used to code sign the application binary.
- 3rd Party Mac Developer Installer certificate – used to sign the
.pkg
installer required for notarization and App Store Connect submission.
Both are mandatory. If either is missing or unavailable, your CI/CD pipeline will fail during signing or upload.
Why Importing Certificates Separately Didn’t Work in GitHub Actions
Initially, I tried importing each certificate using separate steps with apple-actions/import-codesign-certs
. Both .p12
files were valid and correctly configured as GitHub secrets.
However, the issue arose because the action creates a new temporary keychain each time it runs, using the same name by default. Here’s what happened:
- On the first import (Apple Distribution), the keychain was created and the certificate was successfully imported.
On the second import (Installer), the action attempted to create the same keychain again — but since it already existed, the step failed with an error like:
1
A keychain with the same name already exists
- As a result, the second certificate was never imported, and the workflow failed.
- Setting
create-keychain: false
did also not help — the action still tried to manage the keychain somehow and ran into the same conflict.
While importing both certificates separately did work, importing them together in a GitHub Action did not work due to the isolated keychain management behavior of the action.
The Solution: Combine Both Certificates Into One .p12
Rather than trying to work around the keychain creation behavior, the solution was simple and effective: combine both certificates into a single .p12
file. This allowed me to:
- Import both certificates in a single step
- Avoid keychain collisions entirely
- Ensure both certs are available in the same keychain context
How I Combined the Certificates
- Export each certificate from Keychain Access (following the approach in my previous post):
AppleDistribution.p12
InstallerCert.p12
Extract the certs and keys using OpenSSL:
1 2
openssl pkcs12 -in AppleDistribution.p12 -out AppleDistribution.pem -nodes openssl pkcs12 -in InstallerCert.p12 -out InstallerCert.pem -nodes
Combine both into one PEM file:
1
cat AppleDistribution.pem InstallerCert.pem > CombinedCerts.pem
Export combined
.p12
:1 2 3 4 5
openssl pkcs12 -export \ -in CombinedCerts.pem \ -out TimeTraverseHub_CompleteCerts.p12 \ -keypbe AES-256-CBC \ -certpbe AES-256-CBC
Base64 encode for GitHub Actions:
1
base64 -i TimeTraverseHub_CompleteCerts.p12 -o TimeTraverseHub_Certs_base64.txt
Verifying Import in CI
To confirm that both certificates were successfully imported into the keychain during your GitHub Actions workflow, you can use the following step to inspect their metadata:
1
2
3
4
- name: Show certificates info
run: |
security find-certificate -a -c "Apple Distribution" -p | openssl x509 -noout -subject -issuer -dates
security find-certificate -a -c "Installer" -p | openssl x509 -noout -subject -issuer -dates
This will output the subject, issuer, and validity dates for both certificates — helping you confirm their presence and trust level at runtime.
Results
After switching to the combined .p12
approach:
- Both certificates imported successfully in a single step
- Code signing and installer signing worked end-to-end
- Upload to App Store Connect via CI succeeded without errors
Conclusion
This wasn’t just an optimization — it was the only reliable way to get both certificates working in GitHub Actions. The keychain behavior of import-codesign-certs
prevents multiple independent imports from succeeding in the same workflow run.
If you’re building and distributing macOS (or Catalyst) apps in CI, I highly recommend combining your Apple Distribution and Installer certificates into one .p12
. It solves the above described problems and ensures a smooth, repeatable pipeline.
Ever ran into the same problem? How did you solve this issue? Was this blog post helpful for you? Let’s share experiences, I am curios to read how it went for you!
Until the next post, happy coding, everyone!
Disclaimer: As with the previous post, I had some help from ChatGPT to get to the root of how to solve this (instead of searching the web) and shape this blog post. The final content is mine, but for the troubleshooting part, ChatGPT was an invaluable help. Additionally, the title image for this post was generated using AI.