diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b73b5d9..11b879a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -88,8 +88,14 @@ jobs: -k "$KEYCHAIN_PASS" "$KEYCHAIN_PATH" CERT_PEM="$RUNNER_TEMP/certificate.pem" - openssl pkcs12 -in "$CERT_PATH" -clcerts -nokeys \ - -passin "pass:${APPLE_CERTIFICATE_PASSWORD}" -out "$CERT_PEM" + # OpenSSL 3 disables legacy RC2-40-CBC used by older PKCS#12 exports. + # Try modern decode first; fall back to -legacy for existing secrets. + if ! openssl pkcs12 -in "$CERT_PATH" -clcerts -nokeys \ + -passin "pass:${APPLE_CERTIFICATE_PASSWORD}" -out "$CERT_PEM" 2>/dev/null + then + openssl pkcs12 -in "$CERT_PATH" -clcerts -nokeys -legacy \ + -passin "pass:${APPLE_CERTIFICATE_PASSWORD}" -out "$CERT_PEM" + fi SIGN_CERT_SHA256="$(openssl x509 -in "$CERT_PEM" -noout \ -fingerprint -sha256 | cut -d= -f2 | tr -d ':')" IDENTITY="$(security find-identity -p codesigning "$KEYCHAIN_PATH" \ diff --git a/docs/sdlc/changes/2026-09-03-release-p12-openssl3/intent.md b/docs/sdlc/changes/2026-09-03-release-p12-openssl3/intent.md new file mode 100644 index 0000000..4b4da3d --- /dev/null +++ b/docs/sdlc/changes/2026-09-03-release-p12-openssl3/intent.md @@ -0,0 +1,66 @@ +# Intent: Fix OpenSSL 3 PKCS#12 import so releases can ship + +**Status:** approved +**Approved-by:** IchenDEV (user) +**Approved-date:** 2026-09-03 +**Upstream:** Failed Release run for tag v0.0.44 + (https://github.com/IchenDEV/utter/actions/runs/33366312954) + +## Problem + +Tag `v0.0.44` created a GitHub Release page with changelog text but no DMG. +Release Candidate Tests passed. `Sign, Verify & Publish` failed in +**Import signing certificate** while extracting the leaf cert from the +configured `.p12` with OpenSSL 3: + +```text +Algorithm (RC2-40-CBC : 0) ... unsupported +``` + +`security import` of the PKCS#12 into the temporary keychain succeeded; the +subsequent `openssl pkcs12 -clcerts -nokeys` step did not, so build, notarize, +and publish never ran. `v0.0.44` assets remain empty. + +## Outcome + +A following SemVer tag (proposed `v0.0.45`) completes Sign → Verify → Publish +and attaches a checksummed DMG to the GitHub Release. Certificate import works +on the current macos-26 / OpenSSL 3 runner for the existing +`APPLE_CERTIFICATE_P12` secret without requiring a secret rotate unless the +chosen design explicitly needs one. + +## Scope + +- Affected: GitHub Actions Release workflow signing-cert import; production + release tagging path. +- In scope: make PKCS#12 leaf-cert extraction compatible with OpenSSL 3 when + the bag uses legacy RC2-40-CBC; ship a new release after the fix is on `main`. +- Non-goals: changing codesign identity, notarization credentials, app product + behavior, or rewriting the self-signed cert generator unless needed for the + same runner failure mode. + +## Constraints + +- High-risk lane: signing / release / production publish. +- Do not fall back to ad-hoc signing when configured signing fails. +- Prefer keeping the existing GitHub secret; rotate only if import still fails + after a runner-compatible extraction path. +- `v0.0.44` already exists as a published release without assets; do not + overwrite its assets. Ship `v0.0.45` (or next free SemVer) instead. + +## Acceptance criteria + +- Release workflow import step completes on macos-26 with the current + `APPLE_CERTIFICATE_P12` secret (or a documented replacement secret). +- Tagging a new SemVer on `main` produces a published GitHub Release that + includes `Utter-.dmg` and matching `.sha256`, and passes + `scripts/verify-release-artifact.sh` for the configured signing mode. +- Modern (non-RC2) PKCS#12 inputs still import if supported by the chosen + extraction path (no regression for freshly exported certs). + +## Open questions + +- Prefer `openssl pkcs12 ... -legacy` with fallback, or extract the leaf PEM + from the keychain after `security import` and avoid `openssl pkcs12` for + fingerprinting? +- Confirm next tag is `v0.0.45`. diff --git a/docs/sdlc/changes/2026-09-03-release-p12-openssl3/plan.md b/docs/sdlc/changes/2026-09-03-release-p12-openssl3/plan.md new file mode 100644 index 0000000..ef3ef77 --- /dev/null +++ b/docs/sdlc/changes/2026-09-03-release-p12-openssl3/plan.md @@ -0,0 +1,36 @@ +# Plan: Fix OpenSSL 3 PKCS#12 import so releases can ship + +**Status:** approved +**Approved-by:** IchenDEV (user) +**Approved-date:** 2026-09-03 +**Upstream:** docs/sdlc/changes/2026-09-03-release-p12-openssl3/spec.md + +## Work items + +1. Update `.github/workflows/release.yml` **Import signing certificate**: + try `openssl pkcs12 ... -clcerts -nokeys` without `-legacy`; on + non-zero exit, retry with `-legacy`; keep fingerprint / identity / + trust steps unchanged. +2. Add SDLC `verification.md` after checks; keep this change-bundle + docs in sync through the PR. +3. Open PR to `main`; run `bash scripts/sdlc-checks.sh`, + `bash scripts/ci-basic-checks.sh`, and `swift test` as required for + the gate (workflow-only change still needs the repository checks). +4. After merge, create annotated tag `v0.0.45` on `main` and push the + tag to trigger Release; confirm DMG + sha256 publish. +5. Leave `v0.0.44` untouched. + +## Verification plan + +| Check | How | +|---|---| +| Dual-path OpenSSL accepts `-legacy` on OpenSSL 3 | Local `openssl pkcs12 -help` / dry extract if a fixture exists | +| SDLC / CI basic / unit tests | `sdlc-checks.sh`, `ci-basic-checks.sh`, `swift test` | +| Real release publish | GitHub Actions Release for `v0.0.45`; assets present | +| No overwrite of `v0.0.44` | `gh release view v0.0.44` still has empty or prior assets unchanged | + +## Out of scope this PR + +- Regenerating `APPLE_CERTIFICATE_P12` +- Changing `scripts/create-signing-cert.sh` default export cipher +- Product / app code changes diff --git a/docs/sdlc/changes/2026-09-03-release-p12-openssl3/spec.md b/docs/sdlc/changes/2026-09-03-release-p12-openssl3/spec.md new file mode 100644 index 0000000..e2b6568 --- /dev/null +++ b/docs/sdlc/changes/2026-09-03-release-p12-openssl3/spec.md @@ -0,0 +1,64 @@ +# Spec: Fix OpenSSL 3 PKCS#12 import so releases can ship + +**Status:** approved +**Approved-by:** IchenDEV (user) +**Approved-date:** 2026-09-03 +**Upstream:** docs/sdlc/changes/2026-09-03-release-p12-openssl3/intent.md + +## Context + +`.github/workflows/release.yml` imports `APPLE_CERTIFICATE_P12` into a +temporary keychain with `security import`, then runs: + +```bash +openssl pkcs12 -in "$CERT_PATH" -clcerts -nokeys \ + -passin "pass:${APPLE_CERTIFICATE_PASSWORD}" -out "$CERT_PEM" +``` + +to obtain a leaf PEM for SHA-256 fingerprinting and (for non-Developer-ID) +trust. On the macos-26 runner, Homebrew OpenSSL 3 rejects the secret's +legacy RC2-40-CBC PKCS#12 encryption. Keychain import already succeeded in +the failed `v0.0.44` run; only the OpenSSL extraction failed. + +## Design + +1. Keep `security import` unchanged. +2. Extract the leaf PEM with a two-step OpenSSL call: + - First try modern `openssl pkcs12 ...` (no `-legacy`) for freshly + exported AES-style PKCS#12 files. + - On failure, retry with `-legacy` so RC2-40-CBC bags work on OpenSSL 3. +3. Leave fingerprint / identity / trust / keychain search-list logic as-is. +4. Do not rotate secrets unless the dual-path import still fails on CI. +5. After the fix is on `main`, tag `v0.0.45`. Do not mutate the empty + `v0.0.44` release assets. + +Non-goals: changing notarization, build-app signing flags, or the local +`scripts/create-signing-cert.sh` export defaults in this change (optional +follow-up: export with modern ciphers to avoid `-legacy` long-term). + +## Safety and failure modes + +- Signing identity and secret material stay in the existing protected + `production` environment; no new credentials. +- If both OpenSSL attempts fail, the job exits non-zero before any + codesign / notarize / publish step (same fail-closed behavior as today). +- No ad-hoc unsigned fallback. +- Publish still refuses to replace an existing tag's release assets. + +## Test strategy + +- Local: dry-run the dual-path `openssl pkcs12` snippet against a throwaway + modern PKCS#12 and, if available, a legacy-encrypted fixture; confirm + `-legacy` is accepted by runner-equivalent OpenSSL 3. +- CI: push fix via PR checks; after merge, push `v0.0.45` and observe + Sign → Verify → Publish success with DMG + sha256 attached. +- Acceptance: release page for `v0.0.45` has artifacts; `v0.0.44` unchanged. + +## Rollout and rollback + +1. Merge the workflow fix to `main`. +2. Tag `v0.0.45` on the merge commit; let Release workflow publish. +3. Stop if import still fails; diagnose secret format before retagging. +4. Rollback: revert the workflow commit on `main`; leave any successful + `v0.0.45` assets in place (immutable). Failed tags get a newer SemVer + after the next fix—do not force-overwrite published assets. diff --git a/docs/sdlc/changes/2026-09-03-release-p12-openssl3/verification.md b/docs/sdlc/changes/2026-09-03-release-p12-openssl3/verification.md new file mode 100644 index 0000000..054a0c1 --- /dev/null +++ b/docs/sdlc/changes/2026-09-03-release-p12-openssl3/verification.md @@ -0,0 +1,36 @@ +# Verification: Fix OpenSSL 3 PKCS#12 import so releases can ship + +**Status:** pending approval +**Approved-by:** — +**Approved-date:** — +**Upstream:** docs/sdlc/changes/2026-09-03-release-p12-openssl3/plan.md + +## Evidence + +| Check | Result | Evidence | +|---|---|---| +| `openssl pkcs12 -help` shows `-legacy` | Pass | Local OpenSSL 3.6.3 lists `-legacy` | +| Dual-path snippet in `release.yml` | Pass | Modern attempt then `-legacy` fallback | +| `bash scripts/sdlc-checks.sh` | Pass | "SDLC checks passed." | +| `bash scripts/ci-basic-checks.sh` | Pass | "Basic CI checks passed." | +| PR CI Contract & Tests | Pass | https://github.com/IchenDEV/utter/actions/runs/33720711302/job/100539091545 | +| PR CI Release-style App Build | Pass | https://github.com/IchenDEV/utter/actions/runs/33720711302/job/100539091795 | +| PR CI SDLC Gate | Pass | https://github.com/IchenDEV/utter/actions/runs/33720711302/job/100541624657 | +| Release `v0.0.45` assets | Pending | After tag push post-merge | + +## Acceptance criteria + +- Import step compatible with OpenSSL 3 legacy PKCS#12 — implemented; confirm on Release job +- `v0.0.45` publishes DMG + sha256 — pending tag after merge +- Modern PKCS#12 path still attempted first — pass (workflow source) +- `v0.0.44` left unchanged — pending post-release check + +## Residual risk + +- If the p12 password or blob is wrong, both OpenSSL attempts fail closed (same as before). Owner: release maintainer. +- Runner OpenSSL without `-legacy` would fail the fallback; macos-26 currently ships OpenSSL 3 with `-legacy`. Owner: release maintainer. +- Final publish proof depends on the production Release environment secrets remaining valid. Owner: release maintainer. + +## Decision + +Pending human approval of verification evidence; tag `v0.0.45` only after merge + verification approval.