← Back to DevBytes

macOS Developer Certificates

Introduction to macOS Developer Certificates

A macOS Developer Certificate is a digital certificate issued by Apple that allows developers to sign their applications and verify their identity. When you build an app for macOS—whether for distribution through the Mac App Store or direct download from your website—you must sign it with a valid developer certificate. This signature tells macOS that the app comes from a known, trusted source and has not been tampered with since it was signed.

Without proper code signing, macOS users will encounter intimidating Gatekeeper warnings, and the system may refuse to run the application altogether. Understanding how developer certificates work is therefore essential for any serious macOS developer.

What Is a macOS Developer Certificate?

At its core, a macOS Developer Certificate is an X.509 digital certificate that binds your developer identity to a public/private key pair. Apple issues these certificates through the Apple Developer Program after verifying your identity. The certificate itself contains your developer team identifier, a serial number, an expiration date, and Apple's digital signature.

There are several types of certificates you may encounter:

Each certificate type serves a specific distribution channel. Using the wrong certificate will cause submission failures or prevent the app from launching on user machines.

Why Developer Certificates Matter

Code signing is not just a bureaucratic hurdle—it is the foundation of macOS security. Here is why certificates matter:

Prerequisites

Before you can create and use macOS Developer Certificates, you need the following:

Creating a Developer Certificate

Method 1: Using Xcode Automatic Signing

The easiest way to create and manage certificates is through Xcode's automatic signing feature. When enabled, Xcode will create the necessary certificates and provisioning profiles for you.

  1. Open your project in Xcode.
  2. Select your app target in the project navigator.
  3. Go to the Signing & Capabilities tab.
  4. Check Automatically manage signing.
  5. Select your development team from the dropdown.

Xcode will create a Mac Development certificate if one does not exist and register your Mac as a development device.

Method 2: Creating Certificates Manually

For more control, especially for Developer ID certificates used in direct distribution, you may want to create certificates manually through the Apple Developer portal.

First, generate a Certificate Signing Request (CSR) using the Keychain Access app or the command line:

# Generate a private key and CSR using OpenSSL
openssl req -new -newkey rsa:2048 -keyout developer_id.key \
  -out developer_id.csr -subj "/emailAddress=you@example.com/CN=Developer ID Application: Your Name (TEAMID)"

Then, log in to developer.apple.com/account, navigate to Certificates, Identifiers & Profiles, click the plus button, select the certificate type you need, and upload the CSR file. Apple will generate and let you download the certificate.

Once downloaded, add the certificate to your keychain:

# Import the certificate into your login keychain
security import developer_id.p12 -k ~/Library/Keychains/login.keychain-db \
  -P "your_password" -T /usr/bin/codesign

Code Signing Your Application

After obtaining a certificate, you sign your application using the codesign command-line tool. First, identify your certificate:

# List available signing identities
security find-identity -v -p codesigning

# Typical output:
# 1) Developer ID Application: Your Name (TEAMID)
#    1 valid identities found

Once you know the exact name of your signing identity, sign your app:

# Sign the application bundle
codesign --force --deep --options runtime \
  --sign "Developer ID Application: Your Name (TEAMID)" \
  /path/to/YourApp.app

# Verify the signature
codesign --verify --verbose=4 /path/to/YourApp.app

The --options runtime flag enables the Hardened Runtime, which is required for notarization. The --deep flag recursively signs nested code, though Apple now recommends signing nested components individually for better control.

Signing with Entitlements

If your app uses entitlements such as App Sandbox or iCloud, you must pass an entitlements file during signing:

# Sign with an entitlements file
codesign --force --options runtime \
  --entitlements YourApp.entitlements \
  --sign "Developer ID Application: Your Name (TEAMID)" \
  /path/to/YourApp.app

An example entitlements file looks like this:

<?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.app-sandbox</key>
    <true/>
    <key>com.apple.security.network.client</key>
    <true/>
    <key>com.apple.security.files.user-selected.read-write</key>
    <true/>
</dict>
</plist>

Notarizing Your Application

Since macOS 10.15, Apple requires all Developer ID distributed apps to be notarized. Notarization is an automated process where Apple scans your software for malicious content. Here is how to notarize your app:

# 1. Create an app-specific password at appleid.apple.com
# 2. Store it in your keychain (one-time setup)
xcrun notarytool store-credentials "AC_PASSWORD" \
  --apple-id "you@example.com" \
  --team-id "TEAMID" \
  --password "your-app-specific-password"

# 3. Zip the app for submission
ditto -c -k --keepParent /path/to/YourApp.app YourApp.zip

# 4. Submit for notarization
xcrun notarytool submit YourApp.zip \
  --keychain-profile "AC_PASSWORD" \
  --wait

# 5. Staple the notarization ticket to the app
xcrun stapler staple /path/to/YourApp.app

# 6. Verify notarization
xcrun stapler validate /path/to/YourApp.app
spctl --assess --verbose=4 /path/to/YourApp.app

The --wait flag tells notarytool to block until the notarization process completes. Once stapled, the notarization ticket travels with the app, so users can verify it even without an internet connection.

Automating the Build and Sign Process

For continuous integration, you will want to automate signing. Here is a sample shell script that builds, signs, notarizes, and staples a macOS app:

#!/bin/bash
set -e

APP_NAME="YourApp"
SCHEME="YourApp"
TEAM_ID="TEAMID"
SIGN_IDENTITY="Developer ID Application: Your Name (TEAMID)"
BUILD_DIR="./build"

# Step 1: Build the app
xcodebuild -scheme "$SCHEME" \
  -configuration Release \
  -derivedDataPath "$BUILD_DIR" \
  build

APP_PATH="$BUILD_DIR/Build/Products/Release/$APP_NAME.app"

# Step 2: Sign the app
codesign --force --options runtime \
  --entitlements "$APP_NAME.entitlements" \
  --sign "$SIGN_IDENTITY" \
  "$APP_PATH"

# Step 3: Verify the signature
codesign --verify --verbose=4 "$APP_PATH"

# Step 4: Create a zip for notarization
ditto -c -k --keepParent "$APP_PATH" "$APP_NAME.zip"

# Step 5: Submit for notarization and wait
xcrun notarytool submit "$APP_NAME.zip" \
  --keychain-profile "AC_PASSWORD" \
  --wait

# Step 6: Staple the notarization ticket
xcrun stapler staple "$APP_PATH"

# Step 7: Final verification
spctl --assess --verbose=4 "$APP_PATH"

echo "Build, sign, and notarization complete!"

Best Practices

Protect Your Private Keys

Your private keys are the most valuable asset in your signing workflow. Anyone with access to your private key can sign apps as you. Store private keys in a secure keychain, use strong passwords, and never commit them to version control. For team environments, consider using a secure key management system or hardware security modules.

Use Separate Certificates for Development and Distribution

Always maintain a clear separation between development and distribution certificates. Development certificates are tied to specific machines and are used only for testing. Distribution certificates should be guarded carefully and used only when releasing software.

Keep Track of Certificate Expiration

Apple developer certificates are valid for approximately five years. An expired certificate means you cannot sign new builds, though previously signed apps continue to work. Monitor expiration dates and renew certificates well in advance. You can check expiration dates in Keychain Access or via the command line:

# Check certificate expiration
security find-certificate -c "Developer ID Application" -p | \
  openssl x509 -noout -dates

Sign Nested Components Individually

While the --deep flag is convenient, Apple recommends signing nested frameworks, helpers, and plugins individually from the inside out. This gives you precise control over entitlements and ensures each component is signed correctly:

# Sign nested frameworks first (inside out)
codesign --force --options runtime \
  --sign "Developer ID Application: Your Name (TEAMID)" \
  YourApp.app/Contents/Frameworks/SomeFramework.framework

# Then sign helper tools
codesign --force --options runtime \
  --sign "Developer ID Application: Your Name (TEAMID)" \
  YourApp.app/Contents/Library/LaunchServices/com.yourcompany.helper

# Finally, sign the main app bundle
codesign --force --options runtime \
  --entitlements YourApp.entitlements \
  --sign "Developer ID Application: Your Name (TEAMID)" \
  YourApp.app

Always Notarize Developer ID Apps

Notarization is not optional for apps distributed outside the Mac App Store. Without notarization, Gatekeeper will block your app on macOS 10.15 and later. Always submit your builds for notarization and staple the resulting ticket before distribution.

Use App-Specific Passwords

When automating notarization, never use your Apple ID password directly. Instead, generate an app-specific password from appleid.apple.com and store it securely in your keychain using notarytool store-credentials. This limits exposure if your CI system is compromised.

Archive Old Certificates Before Revoking

If you need to revoke a compromised certificate, be aware that apps signed with the old certificate will still run until their notarization tickets expire. However, you will not be able to re-sign those builds. Always archive your build artifacts and signing configurations so you can reproduce builds if needed.

Troubleshooting Common Issues

"Code object is not signed at all"

This error occurs when a nested binary within your app bundle is unsigned. Inspect your app bundle and sign all executable components. Use the following command to find unsigned binaries:

# Find all Mach-O binaries in the app bundle
find YourApp.app -type f -exec sh -c \
  'file "$1" | grep -q Mach-O && codesign -dv "$1" 2>/dev/null || echo "UNSIGNED: $1"' \
  _ {} \;

"errSecInternalComponent" During Signing

This often indicates a keychain access issue. Try unlocking your keychain or clearing the codesign access control list:

# Unlock the login keychain
security unlock-keychain -p "your_keychain_password" ~/Library/Keychains/login.keychain-db

# Reset codesign access if needed
security set-key-partition-list -S apple-tool:,apple: -s -k "your_keychain_password" \
  ~/Library/Keychains/login.keychain-db

Notarization Fails with Hardened Runtime Errors

Ensure every binary in your app bundle is signed with the --options runtime flag. Notarization requires the Hardened Runtime on all executable code, including nested frameworks and helpers.

Conclusion

macOS Developer Certificates are the backbone of trustworthy software distribution on Apple's platform. By understanding the different certificate types, mastering the codesign and notarytool workflows, and following security best practices, you can ensure that your applications reach users without Gatekeeper friction or security warnings. While the signing and notarization process may seem complex at first, automating it with scripts and CI pipelines makes it a seamless part of your release workflow. Invest the time to get it right—your users' security and your reputation as a developer depend on it.

— Ad —

Google AdSense will appear here after approval

← Back to all articles