Skip to content

Commit ef111c0

Browse files
committed
fix(ci): provide BrowserOS passkey profile
1 parent 7aa5ae1 commit ef111c0

6 files changed

Lines changed: 102 additions & 5 deletions

File tree

.github/scripts/macos-signing-keychain.sh

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,13 @@ owned_cert_path() {
2929
esac
3030
}
3131

32+
owned_profile_path() {
33+
case "$1" in
34+
"$RUNNER_TEMP"/browseros-passkey-profile-*.provisionprofile) return 0 ;;
35+
*) return 1 ;;
36+
esac
37+
}
38+
3239
owned_keychains_file() {
3340
case "$1" in
3441
"$RUNNER_TEMP"/browseros-ci-original-keychains-*.txt) return 0 ;;
@@ -70,12 +77,17 @@ append_env() {
7077
fi
7178
}
7279

73-
decode_certificate() {
74-
local output="$1"
75-
if printf '%s' "$MACOS_CERTIFICATE_P12" | base64 --decode > "$output" 2>/dev/null; then
80+
decode_base64_value() {
81+
local value="$1"
82+
local output="$2"
83+
if printf '%s' "$value" | base64 --decode > "$output" 2>/dev/null; then
7684
return 0
7785
fi
78-
printf '%s' "$MACOS_CERTIFICATE_P12" | base64 -D > "$output"
86+
printf '%s' "$value" | base64 -D > "$output"
87+
}
88+
89+
decode_certificate() {
90+
decode_base64_value "$MACOS_CERTIFICATE_P12" "$1"
7991
}
8092

8193
resolve_codesigning_identity() {
@@ -156,6 +168,10 @@ setup_keychain() {
156168

157169
local run_tag="${GITHUB_RUN_ID:-local}-${GITHUB_RUN_ATTEMPT:-1}"
158170
local cert_path="$RUNNER_TEMP/browseros-signing-cert-$run_tag.p12"
171+
local profile_path=""
172+
if [ -n "${PROD_MACOS_BROWSEROS_PASSKEY_PROFILE_B64:-}" ]; then
173+
profile_path="$RUNNER_TEMP/browseros-passkey-profile-$run_tag.provisionprofile"
174+
fi
159175
local keychain_path="$RUNNER_TEMP/browseros-ci-signing-$run_tag.keychain-db"
160176
local original_keychains_file="$RUNNER_TEMP/browseros-ci-original-keychains-$run_tag.txt"
161177
local listed_keychains_file="$RUNNER_TEMP/browseros-ci-listed-keychains-$run_tag.txt"
@@ -191,6 +207,7 @@ setup_keychain() {
191207

192208
{
193209
printf 'cert_path=%s\n' "$cert_path"
210+
printf 'profile_path=%s\n' "$profile_path"
194211
printf 'keychain_path=%s\n' "$keychain_path"
195212
printf 'original_default_keychain=%s\n' "$original_default_keychain"
196213
printf 'original_keychains_file=%s\n' "$original_keychains_file"
@@ -200,10 +217,20 @@ setup_keychain() {
200217
trap cleanup_after_setup_error ERR
201218

202219
rm -f "$cert_path"
220+
if owned_profile_path "$profile_path"; then
221+
rm -f "$profile_path"
222+
fi
203223
security delete-keychain "$keychain_path" >/dev/null 2>&1 || true
204224
rm -f "$keychain_path"
205225

206226
decode_certificate "$cert_path"
227+
if [ -n "$profile_path" ]; then
228+
# The profile is an app-ID-specific authorization document. Keep it in
229+
# runner-owned temporary storage so neither source checkout nor artifact
230+
# staging can accidentally retain it after signing.
231+
decode_base64_value "$PROD_MACOS_BROWSEROS_PASSKEY_PROFILE_B64" "$profile_path"
232+
chmod 600 "$profile_path"
233+
fi
207234
security create-keychain -p "$MACOS_KEYCHAIN_PASSWORD" "$keychain_path"
208235
security set-keychain-settings -lut 21600 "$keychain_path"
209236
security unlock-keychain -p "$MACOS_KEYCHAIN_PASSWORD" "$keychain_path"
@@ -232,8 +259,10 @@ setup_keychain() {
232259
append_env "${GITHUB_ENV:-}" MACOS_CERTIFICATE_NAME "$codesign_identity"
233260
append_env "${GITHUB_ENV:-}" MACOS_KEYCHAIN_PATH "$keychain_path"
234261
append_env "${GITHUB_ENV:-}" MACOS_SIGNING_STATE_PATH "$state_path"
262+
append_env "${GITHUB_ENV:-}" PROD_MACOS_BROWSEROS_PASSKEY_PROFILE_PATH "$profile_path"
235263
append_env "${GITHUB_OUTPUT:-}" codesign_identity "$codesign_identity"
236264
append_env "${GITHUB_OUTPUT:-}" keychain_path "$keychain_path"
265+
append_env "${GITHUB_OUTPUT:-}" passkey_profile_path "$profile_path"
237266
append_env "${GITHUB_OUTPUT:-}" state_path "$state_path"
238267
trap - ERR
239268
}
@@ -248,6 +277,7 @@ cleanup_keychain() {
248277
fi
249278

250279
local cert_path=""
280+
local profile_path=""
251281
local keychain_path=""
252282
local original_default_keychain=""
253283
local original_keychains_file=""
@@ -256,6 +286,7 @@ cleanup_keychain() {
256286
while IFS= read -r state_line; do
257287
case "$state_line" in
258288
cert_path=*) cert_path="${state_line#cert_path=}" ;;
289+
profile_path=*) profile_path="${state_line#profile_path=}" ;;
259290
keychain_path=*) keychain_path="${state_line#keychain_path=}" ;;
260291
original_default_keychain=*) original_default_keychain="${state_line#original_default_keychain=}" ;;
261292
original_keychains_file=*) original_keychains_file="${state_line#original_keychains_file=}" ;;
@@ -288,6 +319,9 @@ cleanup_keychain() {
288319
if owned_cert_path "$cert_path"; then
289320
rm -f "$cert_path"
290321
fi
322+
if owned_profile_path "$profile_path"; then
323+
rm -f "$profile_path"
324+
fi
291325
if owned_smoke_path "$smoke_path"; then
292326
rm -f "$smoke_path"
293327
fi

.github/workflows/nightly-macos-product.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ on:
5656
required: true
5757
PROD_MACOS_NOTARIZATION_TEAM_ID:
5858
required: true
59+
PROD_MACOS_BROWSEROS_PASSKEY_PROFILE_B64:
60+
required: false
5961
SENTRY_DSN:
6062
required: true
6163
SPARKLE_PRIVATE_KEY:
@@ -200,6 +202,7 @@ jobs:
200202
MACOS_CERTIFICATE_P12: ${{ secrets.MACOS_CERTIFICATE_P12 }}
201203
MACOS_CERTIFICATE_PWD: ${{ secrets.MACOS_CERTIFICATE_PWD }}
202204
MACOS_KEYCHAIN_PASSWORD: ${{ secrets.MACOS_KEYCHAIN_PASSWORD }}
205+
PROD_MACOS_BROWSEROS_PASSKEY_PROFILE_B64: ${{ secrets.PROD_MACOS_BROWSEROS_PASSKEY_PROFILE_B64 }}
203206
shell: bash
204207
run: bash .github/scripts/macos-signing-keychain.sh setup
205208

@@ -222,6 +225,7 @@ jobs:
222225
MACOS_KEYCHAIN_PASSWORD: ${{ secrets.MACOS_KEYCHAIN_PASSWORD }}
223226
POSTHOG_API_KEY: ${{ secrets.POSTHOG_API_KEY }}
224227
PROD_MACOS_NOTARIZATION_APPLE_ID: ${{ secrets.PROD_MACOS_NOTARIZATION_APPLE_ID }}
228+
PROD_MACOS_BROWSEROS_PASSKEY_PROFILE_PATH: ${{ steps.macos_signing.outputs.passkey_profile_path }}
225229
PROD_MACOS_NOTARIZATION_PWD: ${{ secrets.PROD_MACOS_NOTARIZATION_PWD }}
226230
PROD_MACOS_NOTARIZATION_TEAM_ID: ${{ secrets.PROD_MACOS_NOTARIZATION_TEAM_ID }}
227231
PRODUCT: ${{ inputs.product }}

.github/workflows/release-macos.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,8 @@ on:
109109
required: false
110110
PROD_MACOS_NOTARIZATION_PWD:
111111
required: false
112+
PROD_MACOS_BROWSEROS_PASSKEY_PROFILE_B64:
113+
required: false
112114
SPARKLE_PRIVATE_KEY:
113115
required: false
114116
SLACK_WEBHOOK_URL:
@@ -335,6 +337,7 @@ jobs:
335337
MACOS_CERTIFICATE_P12: ${{ secrets.MACOS_CERTIFICATE_P12 }}
336338
MACOS_CERTIFICATE_PWD: ${{ secrets.MACOS_CERTIFICATE_PWD }}
337339
MACOS_KEYCHAIN_PASSWORD: ${{ secrets.MACOS_KEYCHAIN_PASSWORD }}
340+
PROD_MACOS_BROWSEROS_PASSKEY_PROFILE_B64: ${{ secrets.PROD_MACOS_BROWSEROS_PASSKEY_PROFILE_B64 }}
338341
shell: bash
339342
run: bash .github/scripts/macos-signing-keychain.sh setup
340343

@@ -354,6 +357,7 @@ jobs:
354357
MACOS_KEYCHAIN_PASSWORD: ${{ secrets.MACOS_KEYCHAIN_PASSWORD }}
355358
POSTHOG_API_KEY: ${{ secrets.POSTHOG_API_KEY }}
356359
PROD_MACOS_NOTARIZATION_APPLE_ID: ${{ secrets.PROD_MACOS_NOTARIZATION_APPLE_ID }}
360+
PROD_MACOS_BROWSEROS_PASSKEY_PROFILE_PATH: ${{ steps.macos_signing.outputs.passkey_profile_path }}
357361
PROD_MACOS_NOTARIZATION_PWD: ${{ secrets.PROD_MACOS_NOTARIZATION_PWD }}
358362
PROD_MACOS_NOTARIZATION_TEAM_ID: ${{ secrets.PROD_MACOS_NOTARIZATION_TEAM_ID }}
359363
R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}

packages/browseros/bos_build/ci_workflow_test.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,7 @@ def test_macos_release_sets_up_ci_keychain_before_build_and_cleans_up(self):
275275

276276
self.assertIn("MACOS_CERTIFICATE_P12", secrets)
277277
self.assertIn("MACOS_CERTIFICATE_PWD", secrets)
278+
self.assertIn("PROD_MACOS_BROWSEROS_PASSKEY_PROFILE_B64", secrets)
278279
self.assertLess(setup_index, build_index)
279280
self.assertLess(build_index, cleanup_index)
280281
self.assertLess(cleanup_index, upload_index)
@@ -285,6 +286,7 @@ def test_macos_release_sets_up_ci_keychain_before_build_and_cleans_up(self):
285286
"MACOS_CERTIFICATE_P12",
286287
"MACOS_CERTIFICATE_PWD",
287288
"MACOS_KEYCHAIN_PASSWORD",
289+
"PROD_MACOS_BROWSEROS_PASSKEY_PROFILE_B64",
288290
):
289291
self.assertEqual(setup["env"][name], f"${{{{ secrets.{name} }}}}")
290292
self.assertEqual(
@@ -295,6 +297,10 @@ def test_macos_release_sets_up_ci_keychain_before_build_and_cleans_up(self):
295297
build["env"]["MACOS_CERTIFICATE_NAME"],
296298
"${{ steps.macos_signing.outputs.codesign_identity }}",
297299
)
300+
self.assertEqual(
301+
build["env"]["PROD_MACOS_BROWSEROS_PASSKEY_PROFILE_PATH"],
302+
"${{ steps.macos_signing.outputs.passkey_profile_path }}",
303+
)
298304
self.assertEqual(cleanup["if"], "always()")
299305
self.assertEqual(
300306
cleanup["env"]["MACOS_SIGNING_STATE_PATH"],
@@ -1847,6 +1853,21 @@ def test_internal_builder_uses_reservation_and_frozen_artifact_source(self):
18471853
build["env"]["ONBOARDING_RESOURCE_VERSION"],
18481854
"${{ inputs.onboarding_version }}",
18491855
)
1856+
self.assertIn(
1857+
"PROD_MACOS_BROWSEROS_PASSKEY_PROFILE_B64",
1858+
triggers["workflow_call"]["secrets"],
1859+
)
1860+
setup = self.named_step(
1861+
workflow, "build", "Import macOS signing certificate"
1862+
)
1863+
self.assertEqual(
1864+
setup["env"]["PROD_MACOS_BROWSEROS_PASSKEY_PROFILE_B64"],
1865+
"${{ secrets.PROD_MACOS_BROWSEROS_PASSKEY_PROFILE_B64 }}",
1866+
)
1867+
self.assertEqual(
1868+
build["env"]["PROD_MACOS_BROWSEROS_PASSKEY_PROFILE_PATH"],
1869+
"${{ steps.macos_signing.outputs.passkey_profile_path }}",
1870+
)
18501871
self.assertNotIn("BROWSERCLAW_ONBOARD_RESOURCE_VERSION", build["env"])
18511872
for secret in (
18521873
"R2_ACCESS_KEY_ID",
@@ -2432,6 +2453,7 @@ def test_setup_imports_p12_and_cleanup_restores_keychain_state(self):
24322453
smoke_path = self.runner_temp / "browseros-ci-codesign-smoke-123-4"
24332454

24342455
self.assertEqual(outputs["codesign_identity"], self.identity_sha1)
2456+
self.assertEqual(outputs["passkey_profile_path"], "")
24352457
self.assertTrue(keychain_path.exists())
24362458
self.assertTrue(state_path.exists())
24372459
self.assertFalse(cert_path.exists())
@@ -2467,6 +2489,28 @@ def test_setup_imports_p12_and_cleanup_restores_keychain_state(self):
24672489
self.assertFalse(keychain_path.exists())
24682490
self.assertFalse(state_path.exists())
24692491

2492+
def test_setup_decodes_passkey_profile_and_cleanup_removes_it(self):
2493+
result = self._run_helper(
2494+
"setup",
2495+
PROD_MACOS_BROWSEROS_PASSKEY_PROFILE_B64="cHJvZmlsZS1ieXRlcw==",
2496+
)
2497+
self.assertEqual(result.returncode, 0, result.stderr + result.stdout)
2498+
outputs = self._outputs()
2499+
profile_path = Path(outputs["passkey_profile_path"])
2500+
self.assertEqual(profile_path.read_bytes(), b"profile-bytes")
2501+
self.assertEqual(profile_path.stat().st_mode & 0o777, 0o600)
2502+
self.assertIn(
2503+
f"PROD_MACOS_BROWSEROS_PASSKEY_PROFILE_PATH={profile_path}",
2504+
self.github_env.read_text(encoding="utf-8").splitlines(),
2505+
)
2506+
2507+
cleanup = self._run_helper(
2508+
"cleanup",
2509+
MACOS_SIGNING_STATE_PATH=outputs["state_path"],
2510+
)
2511+
self.assertEqual(cleanup.returncode, 0, cleanup.stderr + cleanup.stdout)
2512+
self.assertFalse(profile_path.exists())
2513+
24702514
def test_setup_uses_fingerprint_when_common_name_is_duplicated(self):
24712515
result = self._run_helper("setup", CODESIGN_REJECT_COMMON_NAME="1")
24722516
self.assertEqual(result.returncode, 0, result.stderr + result.stdout)

packages/browseros/bos_build/docs/nightly-macos-ci.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,10 @@ Run the Mac runner in the logged-in GUI user's session. Codesign and
235235
as the APFS clone base.
236236
- `uv`, `gh`, depot_tools, Xcode tools, and Chrome.
237237
- The macOS signing identity and notarization credentials.
238+
- The `PROD_MACOS_BROWSEROS_PASSKEY_PROFILE_B64` repository secret containing
239+
BrowserOS's base64-encoded Developer ID provisioning profile. The signing
240+
helper decodes it into runner-owned temporary storage; BrowserOS validates
241+
and embeds it, and unconditional cleanup removes the temporary copy.
238242
- Enough disk for two Chromium outputs and DMGs.
239243

240244
Set these repository variables:

packages/browseros/bos_build/docs/release-ci.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,14 @@ matching signing key and build-time secrets.
210210

211211
Windows signing needs the eSigner secrets and `SPARKLE_PRIVATE_KEY`. macOS uses
212212
repository variables `BROWSEROS_REPO_PATH` and `BROWSEROS_CHROMIUM_SRC` plus
213-
the signing and notarization secrets on the persistent builder.
213+
the signing and notarization secrets on the persistent builder. Signed
214+
BrowserOS releases also require `PROD_MACOS_BROWSEROS_PASSKEY_PROFILE_B64`, a
215+
base64-encoded Developer ID provisioning profile for
216+
`com.browseros.BrowserOS`. The profile must authorize team `8YMKWU47S5`, the
217+
BrowserOS keychain groups, and
218+
`com.apple.developer.web-browser.public-key-credential`; the build validates
219+
those claims before signing. BrowserOS neo does not consume this app-specific
220+
profile.
214221
`BROWSEROS_CHROMIUM_SRC` is a dedicated, CI-owned APFS clone base. Setup keeps
215222
its pinned Chromium identity strict but repairs local Git changes and
216223
BrowserOS-owned output directories before the release runs against a disposable

0 commit comments

Comments
 (0)