Code Signing¶
Code signing ensures your application is trusted by the operating system and not flagged as malware. Nucleus supports signing for Windows and macOS.
Windows¶
PFX Certificate¶
Sign Windows installers (NSIS, MSI, AppX) with a .pfx / .p12 certificate:
windows {
signing {
enabled = true
certificateFile.set(file("certs/certificate.pfx"))
certificatePassword = "your-password"
algorithm = SigningAlgorithm.Sha256
timestampServer = "http://timestamp.digicert.com"
}
}
Signing DSL Reference¶
| Property | Type | Default | Description |
|---|---|---|---|
enabled |
Boolean |
false |
Enable code signing |
certificateFile |
RegularFileProperty |
— | Path to .pfx / .p12 certificate |
certificatePassword |
String? |
null |
Certificate password |
certificateSha1 |
String? |
null |
SHA-1 thumbprint (for store-installed certs) |
certificateSubjectName |
String? |
null |
Subject name of the certificate |
algorithm |
SigningAlgorithm |
Sha256 |
Signing algorithm |
timestampServer |
String? |
null |
Timestamp server URL |
Signing Algorithms¶
| Algorithm | Description |
|---|---|
SigningAlgorithm.Sha1 |
Legacy, for older Windows |
SigningAlgorithm.Sha256 |
Recommended |
SigningAlgorithm.Sha512 |
Strongest |
Common Timestamp Servers¶
| Provider | URL |
|---|---|
| DigiCert | http://timestamp.digicert.com |
| Sectigo | http://timestamp.sectigo.com |
| GlobalSign | http://timestamp.globalsign.com |
Azure Trusted Signing¶
For cloud-based signing with Azure Trusted Signing:
windows {
signing {
enabled = true
azureTenantId = "your-tenant-id"
azureEndpoint = "https://your-region.codesigning.azure.net"
azureCertificateProfileName = "your-profile"
azureCodeSigningAccountName = "your-account"
}
}
CI/CD: Secrets Management¶
Never commit certificates or passwords to source control. Use environment variables or CI secrets:
windows {
signing {
enabled = true
certificateFile.set(file(System.getenv("WIN_CSC_LINK") ?: "certs/certificate.pfx"))
certificatePassword = System.getenv("WIN_CSC_KEY_PASSWORD")
algorithm = SigningAlgorithm.Sha256
timestampServer = "http://timestamp.digicert.com"
}
}
In GitHub Actions:
env:
WIN_CSC_LINK: ${{ secrets.WIN_CSC_LINK }}
WIN_CSC_KEY_PASSWORD: ${{ secrets.WIN_CSC_KEY_PASSWORD }}
Tip: Base64-encode your
Store the content as a GitHub secret, then decode at build time:.pfxfile for CI:
macOS¶
Prerequisites¶
macOS signing requires an Apple Developer ID certificate:
- Enroll in the Apple Developer Program
- Create a "Developer ID Application" certificate in Xcode or the Apple Developer portal
- The certificate must be in your local Keychain (or a CI keychain)
Signing Configuration¶
macOS {
signing {
sign.set(true)
identity.set("Developer ID Application: My Company (TEAMID)")
// keychain.set("/path/to/keychain.keychain-db") // Optional
}
}
Notarization¶
Apple notarization is required for distributing outside the Mac App Store on macOS 10.15+:
macOS {
notarization {
appleID.set("dev@example.com")
password.set("@keychain:AC_PASSWORD")
teamID.set("TEAMID")
}
}
Tip: Use
xcrun notarytool store-credentialsto save credentials in the keychain:
CI/CD: macOS Signing¶
For GitHub Actions, import the certificate into a temporary keychain:
- name: Import certificate
env:
MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }}
MACOS_CERTIFICATE_PWD: ${{ secrets.MACOS_CERTIFICATE_PWD }}
KEYCHAIN_PWD: ${{ secrets.KEYCHAIN_PWD }}
run: |
echo "$MACOS_CERTIFICATE" | base64 -d > certificate.p12
security create-keychain -p "$KEYCHAIN_PWD" build.keychain
security default-keychain -s build.keychain
security unlock-keychain -p "$KEYCHAIN_PWD" build.keychain
security import certificate.p12 -k build.keychain -P "$MACOS_CERTIFICATE_PWD" -T /usr/bin/codesign
security set-key-partition-list -S apple-tool:,apple: -s -k "$KEYCHAIN_PWD" build.keychain
CI/CD: macOS Signing for Universal Binaries¶
When building universal (fat) macOS binaries, lipo invalidates all code signatures. Signing must happen after the universal merge, in the universal-macos CI job.
Nucleus provides a setup-macos-signing composite action (.github/actions/setup-macos-signing) that creates a temporary keychain and imports certificates:
- name: Setup macOS signing
id: signing
uses: ./.github/actions/setup-macos-signing
with:
certificate-base64: ${{ secrets.MAC_CERTIFICATES_P12 }}
certificate-password: ${{ secrets.MAC_CERTIFICATES_PASSWORD }}
Required Secrets¶
| Secret | Description |
|---|---|
MAC_CERTIFICATES_P12 |
Base64-encoded .p12 containing all signing certificates |
MAC_CERTIFICATES_PASSWORD |
Password for the .p12 file |
MAC_DEVELOPER_ID_APPLICATION |
Developer ID Application identity (e.g. "Developer ID Application: Company (TEAMID)") |
MAC_DEVELOPER_ID_INSTALLER |
Developer ID Installer identity (for direct-distribution PKG, if needed) |
MAC_APP_STORE_APPLICATION |
App Store application identity (e.g. "3rd Party Mac Developer Application: Company (TEAMID)") |
MAC_APP_STORE_INSTALLER |
App Store installer identity (e.g. "3rd Party Mac Developer Installer: Company (TEAMID)") |
MAC_PROVISIONING_PROFILE |
Base64-encoded embedded.provisionprofile for sandboxed app |
MAC_RUNTIME_PROVISIONING_PROFILE |
Base64-encoded runtime provisioning profile for sandboxed app |
MAC_NOTARIZATION_APPLE_ID |
Apple ID for notarization |
MAC_NOTARIZATION_PASSWORD |
App-specific password for notarization |
MAC_NOTARIZATION_TEAM_ID |
Apple Team ID for notarization |
Granular Signing (Inside-Out)¶
The build-macos-universal action signs .app bundles using a strict inside-out order to satisfy Apple's code signing requirements:
.dylibfiles (with runtime entitlements).jnilibfiles (with runtime entitlements)- Main executables in
Contents/MacOS/(with app entitlements) - Runtime executables in
Contents/runtime/Contents/Home/bin/(with runtime entitlements) .frameworkbundles- Runtime bundle (
Contents/runtime) - The
.appbundle itself (with app entitlements)
All signing uses --options runtime --timestamp for hardened runtime and timestamping.
Distribution Flows¶
DMG + ZIP (Direct Distribution):
- Non-sandboxed
.appsigned with Developer ID Application identity - Notarized via
xcrun notarytoolafter packaging - DMG is stapled directly; ZIP is extracted,
.appis stapled, then re-zipped
PKG (App Store):
- Sandboxed
.appsigned with 3rd Party Mac Developer Application identity - Provisioning profiles embedded in
Contents/embedded.provisionprofile - PKG built via
productbuild --componentand signed with 3rd Party Mac Developer Installer identity - No notarization needed (Apple reviews via Transporter)
Backward Compatibility¶
All signing is conditional. Without the MAC_* secrets configured, the workflow falls back to ad-hoc signing and electron-builder PKG generation (identical to the unsigned behavior).
Entitlements¶
For apps using certain capabilities (network, file access, JIT), provide entitlements:
macOS {
entitlementsFile.set(project.file("entitlements.plist"))
runtimeEntitlementsFile.set(project.file("runtime-entitlements.plist"))
}
Minimal entitlements.plist for a JVM app:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
<key>com.apple.security.cs.allow-dyld-environment-variables</key>
<true/>
</dict>
</plist>