fix: macos signing checks
This commit is contained in:
parent
5724d05f9d
commit
bcf5324dae
|
|
@ -133,6 +133,13 @@ jobs:
|
|||
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false
|
||||
uses: docker/setup-buildx-action@v4
|
||||
|
||||
- name: Verify macOS signing certificate chain
|
||||
if: github.ref_type != 'tag' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false)
|
||||
env:
|
||||
MACOS_SIGN_P12: ${{ secrets.MACOS_SIGN_P12 }}
|
||||
MACOS_SIGN_PASSWORD: ${{ secrets.MACOS_SIGN_PASSWORD }}
|
||||
run: ./var/macos/verify-macos-sign-p12.sh
|
||||
|
||||
- name: release
|
||||
id: release
|
||||
if: github.ref_type != 'tag' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false)
|
||||
|
|
|
|||
|
|
@ -11,5 +11,3 @@ asciidoc:
|
|||
toclevels: 2
|
||||
nav:
|
||||
- modules/ROOT/nav.adoc
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,2 @@
|
|||
* xref:index.adoc[Index]
|
||||
* xref:signing.adoc[Signing]
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
= Developer notes
|
||||
|
||||
TODO.
|
||||
|
|
@ -36,24 +36,39 @@ openssl req -new -key developer_id_app.key -out developer_id_app.csr \
|
|||
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:
|
||||
Build a `.p12` that includes the **full** chain: leaf + Developer ID G2 intermediate + Apple Root CA.
|
||||
|
||||
The Apple Root **must** be present. With only leaf + G2, quill embeds a designated requirement of the form `certificate root[field.1.2.840.113635.100.6.2.6]`. On macOS that resolves to Apple Root CA (which does not have that OID), so AMFI SIGKILLs the binary with `does not satisfy its designated Requirement` even though notarization still succeeds. A correct chain produces `certificate 1[...]` instead.
|
||||
|
||||
```sh
|
||||
curl -fsSLO https://www.apple.com/certificateauthority/DeveloperIDG2CA.cer
|
||||
curl -fsSLO https://www.apple.com/appleca/AppleIncRootCertificate.cer
|
||||
|
||||
openssl x509 -inform DER -in developerID_application.cer -out developerID_application.pem
|
||||
openssl x509 -inform DER -in DeveloperIDG2CA.cer -out DeveloperIDG2CA.pem
|
||||
openssl x509 -inform DER -in AppleIncRootCertificate.cer -out AppleRootCA.pem
|
||||
|
||||
# Chain file: intermediate then root (leaf is passed separately via -in).
|
||||
cat DeveloperIDG2CA.pem AppleRootCA.pem > chain.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 \
|
||||
-certfile chain.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.
|
||||
Confirm the `.p12` has three certificates before base64-encoding:
|
||||
|
||||
```sh
|
||||
openssl pkcs12 -in Certificates.p12 -nodes -passin pass:"$MACOS_SIGN_PASSWORD" 2>/dev/null \
|
||||
| grep -c "BEGIN CERTIFICATE"
|
||||
# expect: 3
|
||||
```
|
||||
|
||||
If you already have a Mac with the certificate in Keychain Access, you can export a `.p12` from there instead — include the full chain when exporting.
|
||||
|
||||
#### 2. Create the notarization API key
|
||||
|
||||
|
|
@ -90,7 +105,7 @@ All five must be present for signing to run. GoReleaser enables the step when `M
|
|||
|
||||
| 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`. |
|
||||
| Developer ID Application certificate | ~5 years | Create a new certificate in the Apple portal, export a new **full-chain** `.p12` (leaf + G2 intermediate + Apple Root CA), 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. |
|
||||
|
||||
|
|
@ -98,19 +113,31 @@ After updating secrets, the next release on `main` (via semantic-release) will u
|
|||
|
||||
### Verifying a signed release
|
||||
|
||||
On a Mac, download a `OliveTin-darwin-*.tar.gz` release artifact and run:
|
||||
From any platform (no Mac required), check that quill did **not** emit the broken `certificate root[...]` designated requirement:
|
||||
|
||||
```sh
|
||||
go install github.com/anchore/quill/cmd/quill@latest
|
||||
quill describe OliveTin-darwin-arm64/OliveTin
|
||||
```
|
||||
|
||||
The requirements line must contain `certificate 1[field.1.2.840.113635.100.6.2.6]`. If it says `certificate root[field.1.2.840.113635.100.6.2.6]`, the `.p12` is missing Apple Root CA — rebuild it and update `MACOS_SIGN_P12`.
|
||||
|
||||
On a Mac, also run:
|
||||
|
||||
```sh
|
||||
tar -xzf OliveTin-darwin-arm64.tar.gz
|
||||
codesign --verify --strict -vvvv OliveTin-darwin-arm64/OliveTin
|
||||
spctl -a -vv -t execute OliveTin-darwin-arm64/OliveTin
|
||||
```
|
||||
|
||||
A signed and notarized binary should report `accepted` with `source=Notarized Developer ID`.
|
||||
`codesign` should report both `valid on disk` and `satisfies its Designated Requirement`. `spctl` should report `accepted` with `source=Notarized Developer ID`.
|
||||
|
||||
### 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)
|
||||
- CI preflight (3-cert P12): link:https://github.com/OliveTin/OliveTin/blob/main/var/macos/verify-macos-sign-p12.sh[`var/macos/verify-macos-sign-p12.sh`]
|
||||
- CI post-sign check (designated requirement): link:https://github.com/OliveTin/OliveTin/blob/main/var/macos/verify-signed-darwin.sh[`var/macos/verify-signed-darwin.sh`]
|
||||
- link:https://goreleaser.com/customization/notarize/[GoReleaser notarization docs]
|
||||
|
||||
## Windows release signing (SignPath)
|
||||
|
|
|
|||
|
|
@ -52,14 +52,14 @@ properly, see step 6 — you can install it **as your own user (no root)** or
|
|||
|
||||
---
|
||||
|
||||
## 3. Clear the Gatekeeper quarantine
|
||||
## 3. Gatekeeper and notarization
|
||||
|
||||
Because the binary is downloaded from the internet and is **not notarized by
|
||||
Apple**, macOS Gatekeeper will block the first run with a message like
|
||||
*"OliveTin can't be opened because Apple cannot check it for malicious
|
||||
software."*
|
||||
Current release binaries are **Developer ID signed and notarized** by Apple.
|
||||
After extract, you should be able to run `./OliveTin` normally.
|
||||
|
||||
Remove the quarantine attribute so it will run:
|
||||
If Gatekeeper still blocks an older (unsigned) build, or you see a prompt that
|
||||
Apple cannot check the binary for malicious software, clear the quarantine
|
||||
attribute:
|
||||
|
||||
```sh
|
||||
xattr -dr com.apple.quarantine ./OliveTin
|
||||
|
|
@ -356,9 +356,10 @@ tail -f /usr/local/var/log/olivetin.log
|
|||
**"Bad CPU type in executable"** — you downloaded the wrong architecture. Get
|
||||
the `arm64` build for Apple Silicon, `amd64` for Intel (see step 1).
|
||||
|
||||
**Gatekeeper still blocks it** — re-run the `xattr -dr com.apple.quarantine`
|
||||
command in step 3, or approve the app under **System Settings → Privacy &
|
||||
Security**.
|
||||
**Gatekeeper still blocks it** — for older unsigned builds, re-run
|
||||
`xattr -dr com.apple.quarantine ./OliveTin` (see step 3), or approve the app
|
||||
under **System Settings → Privacy & Security**. Current signed releases should
|
||||
not need this.
|
||||
|
||||
**It runs but the page won't load** — check that nothing else is using port
|
||||
1337 (`lsof -i :1337`), and that you're browsing to `http://` (not `https://`).
|
||||
|
|
|
|||
|
|
@ -0,0 +1,65 @@
|
|||
#!/usr/bin/env bash
|
||||
# Preflight: ensure MACOS_SIGN_P12 has the full Developer ID chain
|
||||
# (leaf + Developer ID G2 + Apple Root CA). Quill embeds an unsatisfiable
|
||||
# designated requirement when Apple Root CA is missing — see signing.adoc.
|
||||
set -euo pipefail
|
||||
|
||||
if [[ -z "${MACOS_SIGN_P12:-}" ]]; then
|
||||
echo "MACOS_SIGN_P12 unset; skipping macOS signing preflight."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [[ -z "${MACOS_SIGN_PASSWORD:-}" ]]; then
|
||||
echo "MACOS_SIGN_PASSWORD is required when MACOS_SIGN_P12 is set." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
tmpdir="$(mktemp -d)"
|
||||
trap 'rm -rf "${tmpdir}"' EXIT
|
||||
|
||||
p12_path="${tmpdir}/Certificates.p12"
|
||||
# GitHub secrets may include trailing newlines; strip them before decode.
|
||||
printf '%s' "${MACOS_SIGN_P12}" | tr -d '\n\r' | base64 -d >"${p12_path}"
|
||||
|
||||
pem_out="${tmpdir}/certs.pem"
|
||||
extract_p12() {
|
||||
local extra_args=("${@}")
|
||||
openssl pkcs12 -in "${p12_path}" -nodes -passin "pass:${MACOS_SIGN_PASSWORD}" \
|
||||
"${extra_args[@]}" -out "${pem_out}" 2>/dev/null
|
||||
}
|
||||
|
||||
if ! extract_p12; then
|
||||
# OpenSSL 3 may need -legacy for older P12 exports.
|
||||
extract_p12 -legacy
|
||||
fi
|
||||
|
||||
cert_dir="${tmpdir}/certs"
|
||||
mkdir -p "${cert_dir}"
|
||||
awk -v dir="${cert_dir}" '
|
||||
/-----BEGIN CERTIFICATE-----/ { n++; file = sprintf("%s/cert-%02d.pem", dir, n) }
|
||||
n { print > file }
|
||||
' "${pem_out}"
|
||||
|
||||
cert_count="$(find "${cert_dir}" -name 'cert-*.pem' | wc -l | tr -d ' ')"
|
||||
if [[ "${cert_count}" -ne 3 ]]; then
|
||||
echo "MACOS_SIGN_P12 must contain exactly 3 certificates (leaf + G2 + Apple Root CA); found ${cert_count}." >&2
|
||||
echo "Rebuild the .p12 per docs/modules/dev/pages/signing.adoc." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
found_apple_root=0
|
||||
for cert in "${cert_dir}"/cert-*.pem; do
|
||||
subject="$(openssl x509 -in "${cert}" -noout -subject 2>/dev/null || true)"
|
||||
if [[ "${subject}" == *"Apple Root CA"* ]]; then
|
||||
found_apple_root=1
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ "${found_apple_root}" -ne 1 ]]; then
|
||||
echo "MACOS_SIGN_P12 is missing Apple Root CA in the certificate chain." >&2
|
||||
echo "Rebuild the .p12 per docs/modules/dev/pages/signing.adoc." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "MACOS_SIGN_P12 preflight OK (3 certificates, including Apple Root CA)."
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
#!/usr/bin/env bash
|
||||
# Post-sign: fail if quill embedded the broken designated requirement
|
||||
# certificate root[field.1.2.840.113635.100.6.2.6] (missing Apple Root in P12).
|
||||
set -euo pipefail
|
||||
|
||||
if [[ -z "${MACOS_SIGN_P12:-}" ]]; then
|
||||
echo "MACOS_SIGN_P12 unset; skipping darwin signature check."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
REPO_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)"
|
||||
DIST_DIR="${REPO_ROOT}/dist"
|
||||
|
||||
BAD_REQ='certificate root\[field\.1\.2\.840\.113635\.100\.6\.2\.6\]'
|
||||
GOOD_REQ='certificate 1\[field\.1\.2\.840\.113635\.100\.6\.2\.6\]'
|
||||
|
||||
if ! command -v quill >/dev/null 2>&1; then
|
||||
echo "Installing quill..."
|
||||
go install github.com/anchore/quill/cmd/quill@latest
|
||||
export PATH="$(go env GOPATH)/bin:${PATH}"
|
||||
fi
|
||||
|
||||
shopt -s nullglob
|
||||
archives=("${DIST_DIR}"/OliveTin-darwin-*.tar.gz)
|
||||
if [[ "${#archives[@]}" -eq 0 ]]; then
|
||||
echo "No OliveTin-darwin-*.tar.gz archives found under ${DIST_DIR}." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
tmpdir="$(mktemp -d)"
|
||||
trap 'rm -rf "${tmpdir}"' EXIT
|
||||
|
||||
checked=0
|
||||
for archive in "${archives[@]}"; do
|
||||
name="$(basename "${archive}" .tar.gz)"
|
||||
extract_dir="${tmpdir}/${name}"
|
||||
mkdir -p "${extract_dir}"
|
||||
tar -xzf "${archive}" -C "${extract_dir}"
|
||||
|
||||
# Prefer the top-level binary; archives also ship helper scripts named OliveTin.
|
||||
binary="${extract_dir}/${name}/OliveTin"
|
||||
if [[ ! -f "${binary}" ]]; then
|
||||
binary="$(find "${extract_dir}" -type f -path "*/OliveTin" ! -path "*/var/*" | head -n 1)"
|
||||
fi
|
||||
if [[ -z "${binary}" || ! -f "${binary}" ]]; then
|
||||
echo "OliveTin binary not found inside ${archive}." >&2
|
||||
exit 1
|
||||
fi
|
||||
if ! file "${binary}" | grep -qi 'Mach-O'; then
|
||||
echo "Expected a Mach-O binary at ${binary}, got: $(file "${binary}")" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
echo "Checking designated requirement in ${archive}..."
|
||||
describe_out="$(quill describe "${binary}")"
|
||||
if echo "${describe_out}" | grep -qE "${BAD_REQ}"; then
|
||||
echo "Broken designated requirement in ${archive}:" >&2
|
||||
echo " found certificate root[field.1.2.840.113635.100.6.2.6]" >&2
|
||||
echo "MACOS_SIGN_P12 is missing Apple Root CA. Rebuild per docs/modules/dev/pages/signing.adoc." >&2
|
||||
exit 1
|
||||
fi
|
||||
if ! echo "${describe_out}" | grep -qE "${GOOD_REQ}"; then
|
||||
echo "Expected designated requirement with certificate 1[...] not found in ${archive}." >&2
|
||||
echo "${describe_out}" >&2
|
||||
exit 1
|
||||
fi
|
||||
checked=$((checked + 1))
|
||||
done
|
||||
|
||||
echo "Darwin signature check OK (${checked} archive(s); designated requirement uses certificate 1[...])."
|
||||
|
|
@ -51,3 +51,6 @@ done
|
|||
wait_for_stable_file "${ZIP_PATH}"
|
||||
"${SCRIPT_DIR}/build-msi.sh"
|
||||
wait "${goreleaser_pid}"
|
||||
|
||||
# Fail the release job if macOS signing produced an unsatisfiable designated requirement.
|
||||
"${REPO_ROOT}/var/macos/verify-signed-darwin.sh"
|
||||
|
|
|
|||
Loading…
Reference in New Issue