Skip to content

Prototype stable macOS permission identity without Developer ID - #74

Draft
IgorArkhipov wants to merge 7 commits into
ergohaven:mainfrom
IgorArkhipov:igor/macos-stable-release-signing
Draft

Prototype stable macOS permission identity without Developer ID#74
IgorArkhipov wants to merge 7 commits into
ergohaven:mainfrom
IgorArkhipov:igor/macos-stable-release-signing

Conversation

@IgorArkhipov

@IgorArkhipov IgorArkhipov commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Problem

Entropy's existing macOS release artifacts use ad-hoc signatures. Each changed binary receives a new cdhash-only designated requirement, so macOS can show Accessibility or Input Monitoring as enabled while rejecting the updated app.

Entropy does not have Apple Developer Program membership, so Developer ID signing and notarization are unavailable.

Fix

  • Sign macOS release apps with one project-owned self-signed code-signing identity and an explicit certificate-root plus com.ergohaven.entropy designated requirement.
  • Build each release binary before loading the signing credentials, then expose the temporary private key only to /usr/bin/codesign during packaging.
  • Pin the approved public certificate fingerprint in MACOS_SIGNING_CERTIFICATE_SHA1 and reject an unexpected or rotated P12 identity.
  • Verify the final app signature and designated requirement before producing arm64 and Intel DMGs, then delete the temporary keychain and certificate files.
  • Keep ad-hoc local packaging available while requiring stable signing for release artifacts.
  • Tell users upgrading from an unsigned release to grant Accessibility and Input Monitoring once for the project-signed app.

Security and distribution constraints

  • Apple advises against distributing self-signed applications. Gatekeeper still treats these artifacts as unidentified and may require Privacy & Security > Open Anyway.
  • The artifacts are not notarized.
  • Losing or rotating the private key changes the app identity and requires users to grant permissions again.
  • A compromised private key can sign code that matches Entropy's permission identity. Release tags and signing credentials must remain restricted to trusted maintainers.
  • A bundle-ID-only requirement is intentionally rejected because any binary can copy the identifier and target existing Accessibility grants.

Verification

  • cargo test --locked - 653 passed.
  • scripts/test_macos_stable_signing.sh passed.
  • scripts/test_macos_stable_identity_e2e.sh passed on macOS 26.6.2 arm64: two binaries produced different code hashes and the same certificate-root plus bundle-ID designated requirement.
  • The two-stage arm64 release path built the binary first and then produced the .app, .app.zip, and .dmg without invoking Cargo while the key was available.
  • Bash syntax checks, ShellCheck, and git diff --check passed.
  • Actionlint reports only four pre-existing release-note ShellCheck advisories also present on main.

Hosted Intel execution and two-build TCC persistence remain unverified.

Repository owner steps before Ready for review

  1. Create a repository ruleset for v0.* tags that restricts tag creation, updates, and deletion to trusted release maintainers and blocks force updates.
  2. In Keychain Access, create one identity with these exact settings:
    • Name: Entropy Open Source Release Signing
    • Identity Type: Self Signed Root
    • Certificate Type: Code Signing
    • Enable Let me override defaults and choose a long validity period.
  3. Export the certificate and private key together as an encrypted .p12. Store the P12 and password in a maintainer-controlled offline backup.
  4. Add these repository secrets in upstream ergohaven/entropy:
    • MACOS_CERTIFICATE_P12_BASE64: output of base64 -i entropy-release-signing.p12
    • MACOS_CERTIFICATE_PASSWORD: the P12 export password
  5. Add repository variable MACOS_SIGNING_CERTIFICATE_SHA1 with the approved 40-character certificate SHA-1 fingerprint without colons.
  6. Produce two different signed builds with this identity and complete the manual TCC gate below on Apple Silicon and Intel.

Manual release gate

Keep this PR in draft until both architectures pass:

  1. Install the first signed build as /Applications/Entropy.app.
  2. Remove old Entropy entries from Accessibility and Input Monitoring, grant both permissions to the signed app, and verify Universal Symbols.
  3. Replace the app with a second build that uses the same release identity.
  4. Verify Universal Symbols still work without removing or re-adding either permission.
  5. Repeat on Apple Silicon and Intel.

If macOS requests permissions again, do not ship this signing path.

Related: #6
Related: #58

@IgorArkhipov
IgorArkhipov marked this pull request as draft July 13, 2026 01:40
@IgorArkhipov IgorArkhipov changed the title Preserve macOS permission identity across releases Prototype stable macOS permission identity without Developer ID Jul 13, 2026
@IgorArkhipov
IgorArkhipov force-pushed the igor/macos-stable-release-signing branch from 5718a9b to 0856393 Compare July 28, 2026 02:21
jidckii added a commit to jidckii/entropy that referenced this pull request Aug 4, 2026
Ветка в текущем виде не поехала бы ни в PR-гейте, ни в релизе: часть
проблем ломала сборку сразу, часть — только в CI, где их некому было бы
заметить до первого тега.

Блокеры сборки:

- macOS падал и в PR, и в релизе: build_macos_app.sh требует
  assets/entropy.icns, но файл лежал в .gitignore, а ни macos:app, ни
  workflow его не генерировали. На раннере он и не сгенерировался бы —
  там нечем растеризовать SVG. Теперь .icns закоммичен рядом с .ico, и
  clean его не трогает.
- Dockerfile не собирался с zig 0.16: с 0.15 у ziglang поменялась схема
  имён архивов (zig-<arch>-linux-, а не zig-linux-<arch>-), был 404.
- appimagetool 1.9.1 качает runtime с GitHub на каждой сборке и виснет
  без таймаута, если соединение оборвалось (проверено — 17 минут в
  CLOSE-WAIT). В CI это тихий простой job'а до его лимита, хотя
  Dockerfile обещал сборку без сети. Runtime теперь пиннут и передаётся
  через --runtime-file, у curl появились таймауты.
- task windows:* и macos:cross не работали при asdf: ZIG резолвился в
  shim, а cargo запускает build script из каталога крейта в реестре, где
  .tool-versions не находится — shim печатает подсказку вместо версии, и
  cargo-zigbuild падает на разборе semver. Резолвим реальный бинарник.

Публикация релиза:

- В GitHub Release уезжал мусор: dist/**/* тянул сгенерированный конфиг
  nfpm, а dist/macos/* — распакованный Entropy.app целиком. Пути теперь
  перечислены явно, плюс if-no-files-found: error.
- Версия артефактов бралась из Cargo.toml, поэтому предрелиз v0.3.2-rc.1
  выложил бы файлы с именами будущего стабильного v0.3.2, а deb/rpm — с
  неотличимой версией пакета. VERSION прокидывается из тега сквозь
  Taskfile, контейнер и скрипты; nfpm сам разворачивает суффикс в
  0.3.2~rc.1. MSI ProductVersion и CFBundleVersion принимают только
  числовые компоненты, поэтому для них суффикс отбрасывается отдельно.
- Всё, что можно проверить до сборки, ушло в validate: формат тега,
  наличие секции в CHANGELOG и сверка с Cargo.toml. Раньше отсутствие
  секции роняло релиз уже после десятков минут сборки. Заодно это
  страхует расширенный фильтр тегов: легаси-теги v1.13.x/v1.14.x, что
  живут в репозитории, отсекаются обеими проверками.
- Образ для docker:dist пересобирался на каждом релизе с нуля, включая
  компиляцию cargo-zigbuild и libdmg-hfsplus. Собираем его отдельным
  шагом с кэшем слоёв, а DOCKER_IMAGE_READY выключает повторную сборку.

Уклад upstream:

- build.yml вернулся к матрице upstream: снова два macOS-раннера по одной
  арке. Универсальный бандл нужен релизу, а в PR-гейте он лишь удваивал
  время; кроме того, job "Build (macos-15-intel)" мог быть прописан в
  required checks, и его пропажа подвесила бы все PR. Дифф к upstream
  сузился до concurrency и расширения артефакта.
- build_macos_app.sh снова понимает TARGET (одна арка) — так workflow не
  расходится с upstream и с открытым PR ergohaven#74.

Пакеты:

- В packaging/linux/59-vial.rules не хватало второго правила — для
  устройств по Bluetooth, где серийный номер Vial в hidraw не виден.
  Пользователи deb/rpm/arch не получили бы доступ к BLE-клавиатурам.
- В зависимости добавлен libwayland-client: eframe собран с фичей
  wayland и грузит его через dlopen.
- prepare теперь проверяет не имена пакетов, а сами инструменты, и
  сообщает, чего не хватает: набор пакетов покрывает дистрибутивы
  неравномерно (в alpine нет msitools и icnsutils), и раньше нехватка
  всплывала на середине сборки.
- zig и cargo-zigbuild выровнены между .tool-versions, Dockerfile и
  prepare_env.sh: линия cargo-zigbuild рассчитана на конкретный zig,
  поэтому они должны двигаться вместе.

Проверено сборкой с VERSION=0.3.2-rc.1: нативно linux:pkg, linux:appimage
и windows:all, затем полный docker:dist в контейнере с нуля. Метаданные
deb/rpm, ProductVersion в MSI и содержимое AppImage — как ожидалось,
права после контейнера восстановлены, закоммиченные иконки не перезаписаны.
Compile release binaries before unlocking the project signing key, restrict the imported identity to codesign, and reject certificate rotation unless its approved fingerprint is updated.

Exercise the split compile/package path in pull-request CI and cover the designated-requirement generator directly.
@IgorArkhipov
IgorArkhipov force-pushed the igor/macos-stable-release-signing branch from 0856393 to 9f90f13 Compare August 31, 2026 19:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant