docs/mac-app-store-code-signing-guide.md
Related macOS docs:
Mac App Store validation requires the application signing identity to match a certificate embedded in its provisioning profile. Certificate names, fingerprints, and owners change at every rotation, so derive them from the current keychain and profile rather than copying maintainer-specific values into configuration or documentation.
build/electron-builder.mas.yaml owns the
MAS target, entitlements, application ID, and profile path.Keep identity selection automatic unless the executable configuration changes. Do not add a copied certificate name or fingerprint to electron-builder config.
Confirm that the intended current Apple Distribution identity and private key are installed:
security find-identity -v -p codesigning
In the
Apple Developer profile portal,
create a Mac App Store Connect distribution profile for
com.super-productivity.app.
Select the current Apple Distribution certificate shown by the portal. Do not select a superseded legacy Mac App Distribution certificate merely because it has a familiar owner name.
Save the profile as tools/mac-profiles/mas.provisionprofile.
List every certificate embedded in the profile. The script prints its subject and SHA-1 fingerprint; SHA-1 is used here only because macOS identity listings use that identifier.
PROFILE_PATH="tools/mac-profiles/mas.provisionprofile" python3 - <<'PY'
import hashlib
import os
import plistlib
import subprocess
import tempfile
profile = subprocess.run(
["security", "cms", "-D", "-i", os.environ["PROFILE_PATH"]],
check=True,
capture_output=True,
).stdout
certificates = plistlib.loads(profile)["DeveloperCertificates"]
with tempfile.TemporaryDirectory() as directory:
for index, certificate in enumerate(certificates):
path = os.path.join(directory, f"profile-cert-{index}.der")
with open(path, "wb") as output:
output.write(certificate)
subject = subprocess.run(
[
"openssl",
"x509",
"-inform",
"DER",
"-in",
path,
"-noout",
"-subject",
],
check=True,
capture_output=True,
text=True,
).stdout.strip()
fingerprint = hashlib.sha1(certificate).hexdigest().upper()
print(f"{fingerprint} {subject}")
PY
Confirm that at least one fingerprint exactly matches the intended Apple
Distribution identity from security find-identity.
Encode the verified profile:
base64 -i tools/mac-profiles/mas.provisionprofile -o mas-profile.b64
Update the mas_provision_profile GitHub Actions secret. Do not commit the
encoded profile.
Build locally with the same profile:
cp tools/mac-profiles/mas.provisionprofile embedded.provisionprofile
npm run build
npm run dist:mac:mas:buildOnly
Inspect the actual app signature and package:
codesign -dv --verbose=4 \
".tmp/app-builds/mas-universal/Super Productivity.app"
pkgutil --check-signature \
.tmp/app-builds/mas-universal/super*.pkg
Run the Mac App Store workflow and compare its profile-certificate diagnostic with the identity reported during signing. They must refer to the same current certificate before upload.
If Apple reports that the executable was not signed by a certificate contained in the profile:
Do not fix a mismatch by pasting a maintainer name or old fingerprint into configuration.
Create and test replacement certificates, profiles, and CI secrets before revoking the working identities. Follow the rotation runbook; it keeps the current release path available until both replacement build paths pass.