Code Signing - Kryptografische Signierung von Software
Code signing refers to the cryptographic signing of software, scripts, and executable files using a private key to verify their origin and integrity. Signed software verifies who created the code (authenticity) and that it has not been altered since signing (integrity). Applications: Windows Authenticode, macOS Gatekeeper, Android APK signing, kernel modules, UEFI Secure Boot.
Code signing is the process of signing software, scripts, and executable files with a digital certificate. Operating systems, app stores, and deployment systems use these signatures to ensure that the code comes from a trusted developer and has not been tampered with since it was signed.
Basic Technical Principle
How Code Signing Works
On the developer side, the process consists of four steps:
- Compile code →
binary.exe - Calculate the SHA-256 hash of the binary
- Sign the hash with a private key (RSA/ECC) → Signature
- Deliver binary + signature + certificate as
SignedBinary.exe
On the recipient side (OS/user), the following checks are then performed:
- Verify signature using the certificate’s public key
- Calculate hash of the received binary
- Hash comparison: Does the hash match the signed hash?
- Check certificate chain: trusted CA, not expired, not revoked?
The result is either a confirmation ("Verified Developer X") or a warning ("Unknown Developer" in Windows SmartScreen or "Corrupted File").
Why Code Signing Alone Is Not Enough
The 2020 SolarWinds attack highlights the limitations: The attackers had access to SolarWinds’ build infrastructure and delivered a signed malware update to over 18,000 customers. The certificate was completely legitimate—security systems trusted the update unconditionally. Code signing must therefore be supplemented by a secure build infrastructure.
Windows Authenticode
Certificate Types
Windows distinguishes between two validation levels:
- OV (Organization Validation): The issuer verifies the organization. SmartScreen reputation builds up gradually through downloads.
- EV (Extended Validation): Stricter verification; hardware token required. New EV certificates immediately receive a good SmartScreen reputation.
Providers include DigiCert, Sectigo, GlobalSign, and others.
Signing Process (signtool.exe)
# Sign PE binary (Authenticode):
signtool sign /fd SHA256 /tr http://timestamp.digicert.com /td SHA256 `
/f certificate.pfx /p PASSWORD myprogram.exe
# Verify:
signtool verify /pa /v myprogram.exe
# Output: "Successfully verified: myprogram.exe"
Timestamping
> Important: Without a timestamp, the signature becomes invalid as soon as the certificate expires. With an RFC 3161 timestamp, the signature is considered valid if it was created during the certificate’s validity period. Timestamping is free with all CAs and should always be used.
Signing PowerShell scripts
# Set Execution Policy to AllSigned for full code signing enforcement:
Set-ExecutionPolicy AllSigned -Scope LocalMachine
# Sign script:
$cert = Get-ChildItem Cert:\CurrentUser\My -CodeSigningCert
Set-AuthenticodeSignature -FilePath myscript.ps1 -Certificate $cert `
-TimestampServer "http://timestamp.digicert.com"
macOS and iOS Signing
Gatekeeper Levels
macOS Gatekeeper has three levels:
- App Store: Only App Store apps allowed
- App Store + Known Developers: Signed apps from Apple Developer Accounts allowed (recommended)
- Anywhere: Any app allowed (not recommended)
Code Signing on macOS
# Sign a macOS app:
codesign --sign "Developer ID Application: MyCompany Inc. (TEAMID)" \
--timestamp \
--options runtime \
my.app
# Verify:
codesign --verify --verbose my.app
spctl --assess -v my.app
# Inspect contents:
codesign --display --verbose=4 my.app
Notarization (required since macOS Catalina)
# Submit app to Apple for automated malware checking:
xcrun notarytool submit my.app.zip \
--apple-id developer@firma.de \
--password APP_SPECIFIC_PASSWORD \
--team-id TEAMID \
--wait
# Embed notarization ticket into the app (stapling):
xcrun stapler staple my.app
# → App works offline without checking Apple servers!
Hardened Runtime
The --options runtime option enforces various security restrictions: no JIT without entitlement, no DYLD injection, and no debugger attachment without entitlement. Hardened Runtime should always be enabled.
iOS/iPadOS App Signing
All iOS apps must be signed by Apple—no sideloading except via TestFlight or AltStore. Development certificates are used for testing on your own devices; distribution certificates are for the App Store or enterprise distribution. Provisioning profiles define which devices are allowed to run the app.
Android APK Signing
Android Signing Schemes
| Scheme | Android Version | Features |
|---|---|---|
| v1 (JAR Signing) | Legacy | Vulnerable to JAR tricks |
| v2 (APK Signing) | Android 7 | Protects the entire APK |
| v3 (Rotating Keys) | Android 9 | Supports key rotation |
| v4 (Incremental) | Android 11 | For incremental updates |
Signing an APK (apksigner)
# Create keystore:
keytool -genkeypair -v -keystore release.keystore \
-alias myapp -keyalg RSA -keysize 4096 -validity 10000
# Sign APK (v2+v3):
apksigner sign \
--ks release.keystore \
--ks-key-alias myapp \
--out myapp-release.apk \
myapp-unsigned.apk
# Verify:
apksigner verify --verbose myapp-release.apk
> Important: Losing a signing key means the app can never be updated again with the same key. The Play Store ties app updates to the original signing key. Google Play App Signing offers an optional backup of the key. For enterprise apps, a Hardware Security Module (HSM) is recommended.
CI/CD and HSM Integration
The Problem with Simple Approaches
A private key stored in a CI/CD environment variable is accessible to all pipeline jobs. If a CI server is compromised, the key is stolen and can be misused for arbitrary signatures.
Secure Approaches
1. Hardware Security Module (HSM)
The private key never leaves the HSM. The signing operation takes place within the HSM. Providers: AWS CloudHSM, Azure Dedicated HSM, YubiHSM, Thales.
# PKCS#11 interface to the HSM:
openssl pkeyutl -sign -pkeyopt digest:sha256 \
-engine pkcs11 -keyform engine \
-key "pkcs11:token=MyToken;object=CodeSignKey" \
-in hash.bin -out signature.bin
2. Signing Service (Code Signing as a Service)
A dedicated internal service issues signatures. The pipeline sends the binary to the service, which signs it and returns it. An audit log records all signing requests. Providers: SignPath.io, DigiCert ONE, Garasign.
3. AWS Signer / Azure Code Signing
Cloud-based signing without a local key, using IAM access control to determine who can request signatures.
# GitLab example with AWS Signer:
sign-binary:
stage: sign
script:
- aws signer start-signing-job \
--source 's3Payload={bucketName=build-bucket,key=myapp.exe}' \
--destination 's3={bucketName=signed-bucket,prefix=signed/}' \
--profile-name MySigningProfile
only:
- tags # Sign only Git tags!
Revocation and Rotation
When to revoke?
- Private key compromised (server breach, key leak in Git)
- Employee who had the key has left the company
- Certificate is expiring (perform rotation before expiration)
Perform revocation
Contact the CA (e.g., DigiCert Portal) to update the Certificate Revocation List (CRL). OCSP stapling allows apps to check the validity status online.
> Problem: Users with older (cached) software may not check the CRL. In offline mode, Windows also does not check CRL/OCSP when launching signed software, so revoked certificates are accepted.
Key Rotation Best Practices
- Track certificate expiration in the ticket system (renew 90 days before expiration)
- Use the new certificate in parallel so that old signatures remain valid
- Never use the same key for development and production
- Use hardware tokens (YubiKey) or HSMs for production keys
- Rotate keys regularly even without compromise (recommended annually)
Check signed code for revocation
# Windows:
certutil -verify -urlfetch myprogram.exe
# Linux (openssl):
openssl ocsp -issuer issuer.pem -cert signing-cert.pem \
-url http://ocsp.digicert.com -text