super-productivity/docs/update-mac-certificates.md
2025-11-09 16:35:09 +01:00

13 KiB
Raw Permalink Blame History

Update macOS certificates for electron-builder

Mac access required! The instructions below refresh every asset used by the GitHub Actions/macOS runners for Mac App Store (MAS) and direct-download (DMG) builds.

Certificates

1. Clean up old material

⚠️ Note: Deleting old certificates also removes their private keys. If you dont have the private key backed up, generate a new CSR before creating new certificates (so maybe we should not delete the certificates too early?).

  • In https://developer.apple.com/account/resources/certificates/list, revoke the expiring certificates so they cannot be downloaded again by accident.
  • Remove the matching identities from your local keychain (Keychain Access → My Certificates - open -a "Keychain Access" ) so you do not export the wrong private key later.

2. Create a fresh CSR (once)

  1. Open Keychain Access → Certificate Assistant → Request a Certificate From a Certificate Authority…
  2. Enter the Apple ID email tied to the team, select “Saved to disk”, and pick a location for mac-dev-team.csr.
  3. Repeat only if you need a CSR for a different Apple ID/team.

3. Generate the required certificates

Create the following certificates in the developer portal, using the CSR from step 2:

  • Apple Development used for local development/debug builds.
  • Apple Distribution (Apple renamed “Mac App Distribution”) used to sign the MAS app.
  • Mac Installer Distribution used to sign the MAS .pkg that is uploaded via Transporter.
  • Developer ID Application used to sign the notarized DMG build.
  • Developer ID Installer used if you ship a signed installer .pkg for DMG distribution (still required by electron-builder when dist:mac:dl runs). Download each resulting .cer file and note the exact label Apple shows so you can cross-check in CI logs later.

4. Install and export as PKCS#12

  1. Double-click every downloaded .cer so it lands inside the login keychain under “My Certificates”. You should now see each certificate with a disclosure triangle that reveals the paired private key—if the triangle is missing, delete the cert and regenerate it so the private key attaches properly.
  2. Multi-select the identities listed above (adjust if you only target MAS or only Developer ID), right-click → Export Items…, save as all-certs.p12, and choose a strong password (this password becomes the MAC_CERTS_PASSWORD). Confirm the repeated macOS password prompts.
    • Alternatively, run:
      security export -k ~/Library/Keychains/login.keychain-db \
        -t identities -f pkcs12 -P "$MAC_CERTS_PASSWORD" \
        -o all-certs.p12
      
  3. Verify you can re-import all-certs.p12 onto another Mac before proceeding.

5. Prepare CI secrets

  1. Base64 the exported file: base64 -i all-certs.p12 -o all-certs.b64.
  2. Update the GitHub Actions secret MAC_CERTS with the contents of all-certs.b64 and the secret MAC_CERTS_PASSWORD with the password chosen above.
  3. Map those secrets to electron-builders expectations inside your workflow, for example:
    CSC_LINK: ${{ secrets.MAC_CERTS }}
    CSC_KEY_PASSWORD: ${{ secrets.MAC_CERTS_PASSWORD }}
    CSC_IDENTITY_AUTO_DISCOVERY: true
    
    Use CSC_INSTALLER_LINK/CSC_INSTALLER_KEY_PASSWORD if you decide to store installer identities separately.
  4. If you prefer keeping separate secrets, repeat the export step per certificate and upload them with names such as MAC_DISTRIBUTION_CERT, but the current workflow expects a single bundle. Whatever approach you choose, ensure the CI job imports the PKCS#12 into an unlocked keychain before invoking electron-builder.

Provisioning profiles

Important: create/refresh profiles after the new certificates exist, otherwise downloading the profile will still pull the revoked certs.

  1. Go to https://developer.apple.com/account/resources/profiles/list.
  2. Create two new profiles:
    • Type “Mac App Store” → select the Apple Distribution certificate → choose the MAS App ID → download as mas.provisionprofile.
    • Type “Developer ID Application” → select the Developer ID Application certificate → choose the same App ID → download as dl.provisionprofile (optional for most Developer ID apps, but we keep it to satisfy older tooling and entitlements checks).
  3. Move the files into tools/mac-profiles/mas.provisionprofile and tools/mac-profiles/dl.provisionprofile (keep the exact filenames so the build scripts pick them up). If you skip the Developer ID profile, remove tools/mac-profiles/dl.provisionprofile and clear the DL_PROVISION_PROFILE secret so CI doesnt look for it.
  4. Base64-encode them for CI:
    base64 -i tools/mac-profiles/dl.provisionprofile -o dmg-profile.b64
    base64 -i tools/mac-profiles/mas.provisionprofile -o mas-profile.b64
    
  5. Update the GitHub secrets DL_PROVISION_PROFILE (dmg) and MAS_PROVISION_PROFILE (store) with the encoded strings and ensure the workflows pass them to electron-builder (e.g., build.mac.provisioningProfile). Remember to keep hardened runtime enabled (build.mac.hardenedRuntime=true) and entitlements aligned for notarization.

See also:

Build the DMG locally

  1. Create or refresh an app-specific password at https://appleid.apple.com/account/manage (use the same Apple ID as notarization).
  2. Confirm your scripts use xcrun notarytool (Apple blocked altool uploads as of 20231101). electron-builder defaults to notarytool when it detects Xcode 14+, so avoid overriding that behavior.
  3. Run:
    APPLEID="you@example.com" \
    APPLEIDPASS="app-specific-password" \
    rm -Rf app-builds && npm run build && npm run dist:mac:dl
    
  4. The script signs, notarizes (via notarytool), and staples the DMG using the new certificates and provisioning profiles. Validate with spctl --assess -vv --type install path/to/app.

macOS App Store Certificates Guide

(Local + CI workflow with electron-builder — 2025 edition)


1) Overview

To build and publish your Electron app to the Mac App Store (MAS), you need these certificates and a provisioning profile:

Item Purpose Used in
Apple Development Sign local debug/test builds Local only
Apple Distribution Sign the App Store .app Local + CI
Mac Installer Distribution Sign the .pkg uploaded via Transporter Local + CI
Mac App Store provisioning profile Ties the App ID to Apple Distribution Local + CI

You do not need Developer ID certificates or notarization for MAS-only distribution.


2) Generate a new CSR (Certificate Signing Request)

  1. On your Mac, open Keychain Access → Certificate Assistant → Request a Certificate From a Certificate Authority…
  2. Fill in:
  • User Email Address: your Apple ID for the developer team
  • Common Name: e.g. Super Productivity MAS Signing Key
  • CA Email Address: leave blank
  • Request is: Saved to disk
  1. Save as mac-mas.csr.

    This also creates a private key in your login keychain.


3) Create the MAS certificates in the Apple Developer Portal

  1. Go to https://developer.apple.com/account/resources/certificates/list Add
  2. Create each certificate using mac-mas.csr:
  • Apple Development (for local testing)
  • Apple Distribution (replaces “Mac App Distribution”)
  • Mac Installer Distribution (required for .pkg upload)
  1. Download each resulting .cer file.

4) Install and export the identities (for local and CI)

  1. Install: Double-click each .cer to add it under Keychain Access → My Certificates.
  • Each entry must show a disclosure triangle with a private key.
  • If the private key is missing, regenerate the CSR and certificate.
  1. Export: Select the relevant identities → right-click → Export …
  • Format: Personal Information Exchange (.p12)
  • Name: mas-certs.p12
  • Set a strong password (→ MAC_CERTS_PASSWORD).
  1. Verify on a second Mac (optional but recommended):

    security import mas-certs.p12 -k login.keychain-db -P "$MAC_CERTS_PASSWORD"
    security find-identity -v -p codesigning
    

    You should see Apple Distribution and Mac Installer Distribution.


5) Create / refresh the Mac App Store provisioning profile

  1. Go to https://developer.apple.com/account/resources/profiles/list Add Profile
  2. Profile type: Mac App Store
  3. Select:
  • Apple Distribution certificate
  • Your App ID
  1. Download as mas.provisionprofile.

  2. Place it in your repo, e.g.:

    tools/mac-profiles/mas.provisionprofile
    

6) Configure electron-builder

In electron-builder.yml or package.json > build:

mac:
  category: public.app-category.productivity
  hardenedRuntime: true
  provisioningProfile: tools/mac-profiles/mas.provisionprofile
  entitlements: build/entitlements.mas.plist
  entitlementsInherit: build/entitlements.mas.inherit.plist
  target:
    - mas

Ensure your MAS entitlements files are correct and compatible with App Store requirements.


7) Local build (MAS)

With the identities installed in your keychain:

npm run build && npm run dist:mac:mas

Result: dist/mac/YourApp.pkg — signed and ready for App Store upload.


8) Prepare CI secrets

Base64-encode the cert bundle and profile:

base64 -i mas-certs.p12 -o mas-certs.b64
base64 -i tools/mac-profiles/mas.provisionprofile -o mas-profile.b64

Create GitHub Actions secrets (Repo → Settings → Secrets and variables → Actions):

Secret Value
MAC_CERTS contents of mas-certs.b64
MAC_CERTS_PASSWORD the .p12 export password
MAS_PROVISION_PROFILE contents of mas-profile.b64
APPLE_APP_SPECIFIC_PASS Apple ID app-specific password (for upload tooling)

9) GitHub Actions workflow (example)

name: Build macOS MAS
on:
  workflow_dispatch:
  push:
    tags:
      - 'v*'

jobs:
  build-mas:
    runs-on: macos-latest
    steps:
      - uses: actions/checkout@v4

      - name: Decode certificates
        run: echo "${{ secrets.MAC_CERTS }}" | base64 --decode > mas-certs.p12

      - name: Create and unlock keychain
        run: |
          security create-keychain -p "" build.keychain
          security import mas-certs.p12 -k build.keychain -P "${{ secrets.MAC_CERTS_PASSWORD }}" -T /usr/bin/codesign -T /usr/bin/security
          security set-key-partition-list -S apple-tool:,apple: -s -k "" build.keychain
          security list-keychains -s build.keychain
          security default-keychain -s build.keychain
          security unlock-keychain -p "" build.keychain
          security find-identity -v -p codesigning build.keychain

      - name: Write provisioning profile
        run: echo "${{ secrets.MAS_PROVISION_PROFILE }}" | base64 --decode > tools/mac-profiles/mas.provisionprofile

      - name: Install dependencies
        run: npm ci

      - name: Build & sign for MAS
        env:
          CSC_LINK: ${{ secrets.MAC_CERTS }}
          CSC_KEY_PASSWORD: ${{ secrets.MAC_CERTS_PASSWORD }}
          CSC_IDENTITY_AUTO_DISCOVERY: true
          APPLEID: you@example.com
          APPLEIDPASS: ${{ secrets.APPLE_APP_SPECIFIC_PASS }}
        run: npm run build && npm run dist:mac:mas

The set-key-partition-list step grants non-interactive access to the private keys for codesign during CI.


10) Upload to App Store Connect

  1. Find the generated .pkg in dist/.
  2. Upload with Transporter (Apples tool):
  • Open Transporter, sign in with your Apple ID
  • Drag the .pkg and click Deliver
  1. Optional verification:

    pkgutil --check-signature path/to/YourApp.pkg
    

    Expect:

    Signed by "Apple Mac Installer Distribution: <Your Team>"
    Status: signed Apple Software
    

11) Maintenance & rotation

  • Certificates typically expire after 1 year (some teams may have 3-year certs).

  • Before expiry, repeat this guide:

    • Revoke/replace Apple Distribution and Mac Installer Distribution
    • Recreate the Mac App Store provisioning profile
    • Export a new mas-certs.p12, update CI secrets, and (if needed) reimport locally
  • Keep your App-Specific Password valid for uploads.


Quick Reference (What you actually need for MAS)

  • Certificates: Apple Development (local), Apple Distribution, Mac Installer Distribution
  • Profile: Mac App Store provisioning profile
  • No Developer ID / notarization needed for MAS-only
  • Build target: mas
  • Upload: .pkg via Transporter