|
|
@@ -1,31 +1,67 @@
|
|
|
-# macOS release signing
|
|
|
+# 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.
|
|
|
+OliveTin signs release binaries on two platforms:
|
|
|
|
|
|
-Signing is **optional**. If the GitHub secrets below are not all set, GoReleaser skips macOS signing and publishes unsigned binaries (the previous behaviour).
|
|
|
+* **macOS** — Developer ID + notarization via [quill](https://github.com/anchore/quill) inside GoReleaser (optional only when `MACOS_SIGN_P12` is unset).
|
|
|
+* **Windows** — Authenticode via [SignPath Foundation](https://signpath.org/) in a separate GitHub Actions job (required before a draft release is published).
|
|
|
|
|
|
-## Prerequisites
|
|
|
+## 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 or Xcode is required.
|
|
|
+
|
|
|
+Signing is **optional** only when `MACOS_SIGN_P12` is unset — GoReleaser then skips macOS signing and publishes unsigned binaries. When `MACOS_SIGN_P12` is set, the companion macOS secrets below are required or the release fails.
|
|
|
+
|
|
|
+### 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
|
|
|
+### One-time setup
|
|
|
|
|
|
-### 1. Create the signing certificate
|
|
|
+#### 1. Create the signing certificate (OpenSSL, no Mac/Xcode)
|
|
|
+
|
|
|
+Work in a private directory. Keep the private key offline and never commit it.
|
|
|
+
|
|
|
+```sh
|
|
|
+mkdir -p ~/apple-signing && cd ~/apple-signing
|
|
|
+chmod 700 .
|
|
|
+
|
|
|
+openssl genrsa -out developer_id_app.key 2048
|
|
|
+openssl req -new -key developer_id_app.key -out developer_id_app.csr \
|
|
|
+ -subj "/emailAddress=you@example.com/CN=Your Name/C=GB"
|
|
|
+```
|
|
|
|
|
|
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 a certificate of type **Developer ID Application**. Prefer **G2 Sub-CA** if the portal asks.
|
|
|
+3. Upload `developer_id_app.csr` and download the resulting `.cer` (often named `developerID_application.cer`).
|
|
|
+
|
|
|
+Build a `.p12` that includes Apple's Developer ID G2 intermediate:
|
|
|
+
|
|
|
+```sh
|
|
|
+curl -fsSLO https://www.apple.com/certificateauthority/DeveloperIDG2CA.cer
|
|
|
+
|
|
|
+openssl x509 -inform DER -in developerID_application.cer -out developerID_application.pem
|
|
|
+openssl x509 -inform DER -in DeveloperIDG2CA.cer -out DeveloperIDG2CA.pem
|
|
|
+
|
|
|
+# Export password becomes MACOS_SIGN_PASSWORD.
|
|
|
+# On OpenSSL 3 (e.g. Fedora), -legacy improves compatibility with some tooling:
|
|
|
+openssl pkcs12 -export -legacy \
|
|
|
+ -inkey developer_id_app.key \
|
|
|
+ -in developerID_application.pem \
|
|
|
+ -certfile DeveloperIDG2CA.pem \
|
|
|
+ -out Certificates.p12
|
|
|
+```
|
|
|
+
|
|
|
+If you already have a Mac with the certificate in Keychain Access, you can export a `.p12` from there instead; the OpenSSL path above is enough when you do not.
|
|
|
|
|
|
-### 2. Create the notarization API key
|
|
|
+#### 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
|
|
|
+#### 3. Base64-encode the key files
|
|
|
|
|
|
Run on a machine that has the files (Linux or macOS):
|
|
|
|
|
|
@@ -36,7 +72,7 @@ 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
|
|
|
+#### 4. Add GitHub repository secrets
|
|
|
|
|
|
In **Settings → Secrets and variables → Actions**, create:
|
|
|
|
|
|
@@ -48,9 +84,9 @@ In **Settings → Secrets and variables → Actions**, create:
|
|
|
| `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.
|
|
|
+All five must be present for signing to run. GoReleaser enables the step when `MACOS_SIGN_P12` is set; missing companion secrets will fail that release.
|
|
|
|
|
|
-## Renewal
|
|
|
+### Renewal
|
|
|
|
|
|
| Item | Typical lifetime | What to do |
|
|
|
|------|------------------|------------|
|
|
|
@@ -60,7 +96,7 @@ All five must be present for signing to run. Any missing secret disables signing
|
|
|
|
|
|
After updating secrets, the next release on `main` (via semantic-release) will use the new credentials automatically.
|
|
|
|
|
|
-## Verifying a signed release
|
|
|
+### Verifying a signed release
|
|
|
|
|
|
On a Mac, download a `OliveTin-darwin-*.tar.gz` release artifact and run:
|
|
|
|
|
|
@@ -71,8 +107,90 @@ spctl -a -vv -t execute OliveTin-darwin-arm64/OliveTin
|
|
|
|
|
|
A signed and notarized binary should report `accepted` with `source=Notarized Developer ID`.
|
|
|
|
|
|
-## Configuration reference
|
|
|
+### Configuration reference
|
|
|
+
|
|
|
+- GoReleaser: `notarize.macos` in link:https://github.com/OliveTin/OliveTin/blob/main/.goreleaser.yml[`.goreleaser.yml`]
|
|
|
+- CI secrets: link:https://github.com/OliveTin/OliveTin/blob/main/.github/workflows/build-and-release.yml[`.github/workflows/build-and-release.yml`] (`release` step)
|
|
|
+- link:https://goreleaser.com/customization/notarize/[GoReleaser notarization docs]
|
|
|
+
|
|
|
+## 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` ← link:https://github.com/OliveTin/OliveTin/blob/main/docs/modules/dev/signpath/windows-zip.xml[`signpath/windows-zip.xml`]
|
|
|
+* slug `windows-msi` ← link:https://github.com/OliveTin/OliveTin/blob/main/docs/modules/dev/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 link:https://github.com/OliveTin/OliveTin/blob/main/docs/modules/dev/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
|
|
|
|
|
|
-- 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/)
|
|
|
+- Draft release: `release.draft: true` in link:https://github.com/OliveTin/OliveTin/blob/main/.goreleaser.yml[`.goreleaser.yml`]
|
|
|
+- CI job: `sign-windows` in link:https://github.com/OliveTin/OliveTin/blob/main/.github/workflows/build-and-release.yml[`.github/workflows/build-and-release.yml`]
|
|
|
+- Publish helper: link:https://github.com/OliveTin/OliveTin/blob/main/var/windows/signpath-publish-signed.sh[`var/windows/signpath-publish-signed.sh`]
|
|
|
+- SignPath artifact configs (reference only): link:https://github.com/OliveTin/OliveTin/blob/main/docs/modules/dev/signpath/windows-zip.xml[`signpath/windows-zip.xml`], link:https://github.com/OliveTin/OliveTin/blob/main/docs/modules/dev/signpath/windows-msi.xml[`signpath/windows-msi.xml`]
|
|
|
+- link:https://docs.signpath.io/trusted-build-systems/github[SignPath GitHub Actions docs]
|
|
|
+- link:https://docs.signpath.io/artifact-configuration/examples[SignPath artifact configuration examples]
|