# Release signing OliveTin signs release binaries on two platforms: * **macOS** — Developer ID + notarization via [quill](https://github.com/anchore/quill) inside GoReleaser (optional if secrets are missing). * **Windows** — Authenticode via [SignPath Foundation](https://signpath.org/) in a separate GitHub Actions job (required before a draft release is published). ## macOS release signing Release builds can sign and notarize the `darwin` binaries using [quill](https://github.com/anchore/quill) via GoReleaser. This runs on the existing Linux CI runner; no macOS runner is required. Signing is **optional**. If the GitHub secrets below are not all set, GoReleaser skips macOS signing and publishes unsigned binaries (the previous behaviour). ### Prerequisites - An active [Apple Developer Program](https://developer.apple.com/programs/) membership. - A **Developer ID Application** certificate (not "Apple Development" or "Mac App Distribution"). - An [App Store Connect API key](https://appstoreconnect.apple.com/access/integrations/api) with at least **Developer** access. ### One-time setup #### 1. Create the signing certificate 1. Open [Certificates, Identifiers & Profiles](https://developer.apple.com/account/resources/certificates/list). 2. Create a certificate of type **Developer ID Application**. 3. Download the `.cer` file and double-click it to add it to **Keychain Access** on a Mac. 4. In Keychain Access, export the certificate as a **Personal Information Exchange (`.p12`)** file. You will set an export password — remember it; this becomes `MACOS_SIGN_PASSWORD`. #### 2. Create the notarization API key 1. Open [App Store Connect → Users and Access → Integrations → App Store Connect API](https://appstoreconnect.apple.com/access/integrations/api). 2. Create a key with **Developer** role (or Admin). 3. Download the `.p8` file once (it cannot be downloaded again). Note the **Key ID** shown in the portal and the **Issuer ID** at the top of the API keys page. #### 3. Base64-encode the key files Run on a machine that has the files (Linux or macOS): ```sh base64 -w0 < ./Certificates.p12 # MACOS_SIGN_P12 base64 -w0 < ./AuthKey_XXXXXX.p8 # MACOS_NOTARY_KEY ``` On macOS without GNU coreutils, use `base64 -i file | tr -d '\n'`. #### 4. Add GitHub repository secrets In **Settings → Secrets and variables → Actions**, create: | Secret | Value | |--------|-------| | `MACOS_SIGN_P12` | Base64 contents of the `.p12` file | | `MACOS_SIGN_PASSWORD` | Password used when exporting the `.p12` | | `MACOS_NOTARY_KEY` | Base64 contents of the `.p8` file | | `MACOS_NOTARY_KEY_ID` | Key ID from App Store Connect (e.g. `ABC123DEF4`) | | `MACOS_NOTARY_ISSUER_ID` | Issuer UUID from App Store Connect | All five must be present for signing to run. Any missing secret disables signing for that release. ### Renewal | Item | Typical lifetime | What to do | |------|------------------|------------| | Developer ID Application certificate | ~5 years | Create a new certificate in the Apple portal, export a new `.p12`, update `MACOS_SIGN_P12` and `MACOS_SIGN_PASSWORD`. | | App Store Connect API key | Does not expire, but can be revoked | Create a new key if compromised or lost; update `MACOS_NOTARY_KEY`, `MACOS_NOTARY_KEY_ID`, and optionally `MACOS_NOTARY_ISSUER_ID`. | | Apple Developer Program | Annual subscription | Renew membership before it lapses; existing certificates stop working if the account is inactive. | After updating secrets, the next release on `main` (via semantic-release) will use the new credentials automatically. ### Verifying a signed release On a Mac, download a `OliveTin-darwin-*.tar.gz` release artifact and run: ```sh tar -xzf OliveTin-darwin-arm64.tar.gz spctl -a -vv -t execute OliveTin-darwin-arm64/OliveTin ``` A signed and notarized binary should report `accepted` with `source=Notarized Developer ID`. ### Configuration reference - GoReleaser: `notarize.macos` in [`.goreleaser.yml`](.goreleaser.yml) - CI secrets: [`.github/workflows/build-and-release.yml`](.github/workflows/build-and-release.yml) (`release` step) - [GoReleaser notarization docs](https://goreleaser.com/customization/notarize/) ## Windows release signing (SignPath) Windows Authenticode signing uses [SignPath Foundation](https://signpath.org/) (free for qualifying open-source projects). It does **not** use GoReleaser Pro. GoReleaser creates a **draft** GitHub release with unsigned Windows assets. A separate `sign-windows` job submits those assets to SignPath, replaces them on the draft (including updated `checksums.txt`), then publishes the release. Signed artifacts: * `OliveTin.exe` inside `OliveTin-windows-amd64.zip` * nested `OliveTin.exe` and the `OliveTin-windows-amd64.msi` installer (deep signing) Signing is **required** to publish. If SignPath secrets/vars are missing, `sign-windows` fails and the draft stays unpublished. ### Prerequisites - Approval for the [SignPath Foundation open-source program](https://signpath.io/product/open-source). - The SignPath GitHub App installed on the OliveTin organization/repository. - A SignPath project linked to this repository, with a release signing policy. ### One-time setup #### 1. Apply for SignPath Foundation 1. Open https://signpath.io/product/open-source and apply with the OliveTin GitHub repository URL. 2. After approval, create (or confirm) the organization and project in the SignPath portal. #### 2. Install the SignPath GitHub App 1. Install the SignPath GitHub App and grant access to the OliveTin repository. 2. Link the Trusted Build System **GitHub.com** to the SignPath project (required so SignPath can verify the workflow artifact origin). #### 3. Create artifact configurations In the SignPath project, create two artifact configurations with these slugs (must match CI). Paste the XML from the reference copies in this repo (SignPath does **not** load them automatically): * slug `windows-zip` ← [`signpath/windows-zip.xml`](../signpath/windows-zip.xml) * slug `windows-msi` ← [`signpath/windows-msi.xml`](../signpath/windows-msi.xml) Use **Custom** XML in the SignPath UI and paste the file contents. Do **not** use **Upload an artifact sample** on these `.xml` files — SignPath will treat them as XML documents to sign (`xml-file`), which is unavailable on the Foundation/Open Source plan. See [`signpath/README.md`](../signpath/README.md). #### 4. Add GitHub secrets and variables In **Settings → Secrets and variables → Actions**: | Kind | Name | Value | |------|------|-------| | Secret | `SIGNPATH_API_TOKEN` | CI submitter API token from SignPath | | Variable | `SIGNPATH_ORGANIZATION_ID` | SignPath organization ID | | Variable | `SIGNPATH_PROJECT_SLUG` | SignPath project slug (e.g. `olivetin`) | | Variable | `SIGNPATH_SIGNING_POLICY_SLUG` | Signing policy slug (e.g. `release-signing`) | #### 5. Pipeline behaviour On a new semantic-release from `main`: 1. GoReleaser publishes container images and creates a **draft** GitHub release (including unsigned Windows zip/MSI). 2. The build job uploads those Windows files as GitHub Actions artifacts. 3. The `sign-windows` job submits each artifact to SignPath, waits for completion, then runs `var/windows/signpath-publish-signed.sh` to clobber-upload signed files, refresh `checksums.txt`, and undraft the release. All jobs in this chain use GitHub-hosted runners (required by SignPath for OSS projects). ### Verifying a signed release On Windows, download `OliveTin-windows-amd64.msi` or extract `OliveTin.exe` from the zip, then either: * Right-click → **Properties** → **Digital Signatures**, or * Run: ```bat signtool verify /pa OliveTin.exe signtool verify /pa OliveTin-windows-amd64.msi ``` ### Configuration reference - Draft release: `release.draft: true` in [`.goreleaser.yml`](.goreleaser.yml) - CI job: `sign-windows` in [`.github/workflows/build-and-release.yml`](.github/workflows/build-and-release.yml) - Publish helper: [`var/windows/signpath-publish-signed.sh`](var/windows/signpath-publish-signed.sh) - SignPath artifact configs (reference only): [`signpath/windows-zip.xml`](../signpath/windows-zip.xml), [`signpath/windows-msi.xml`](../signpath/windows-msi.xml) - [SignPath GitHub Actions docs](https://docs.signpath.io/trusted-build-systems/github) - [SignPath artifact configuration examples](https://docs.signpath.io/artifact-configuration/examples)