Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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" \
Expand Down
66 changes: 66 additions & 0 deletions docs/sdlc/changes/2026-09-03-release-p12-openssl3/intent.md
Original file line number Diff line number Diff line change
@@ -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-<version>.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`.
36 changes: 36 additions & 0 deletions docs/sdlc/changes/2026-09-03-release-p12-openssl3/plan.md
Original file line number Diff line number Diff line change
@@ -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
64 changes: 64 additions & 0 deletions docs/sdlc/changes/2026-09-03-release-p12-openssl3/spec.md
Original file line number Diff line number Diff line change
@@ -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.
Original file line number Diff line number Diff line change
@@ -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.
Loading