Skip to content

app-server: add macOS device key provider#18431

Closed
euroelessar wants to merge 10 commits into
mainfrom
ruslan/device-key-macos
Closed

app-server: add macOS device key provider#18431
euroelessar wants to merge 10 commits into
mainfrom
ruslan/device-key-macos

Conversation

@euroelessar

@euroelessar euroelessar commented Apr 18, 2026

Copy link
Copy Markdown
Collaborator

Why

The device-key crate needs a platform provider that can keep private keys non-extractable on macOS while preserving the crate-level protection policy and structured signing boundary.

macOS has two useful local protection classes for this API: hardware-backed Secure Enclave keys when available, and OS-protected non-extractable keys as an explicit fallback. Reporting which class was selected keeps that distinction visible to callers.

The Security.framework access-control flags used here provide device-local non-exportability and continuity while the user session is unlocked. They do not require UserPresence or Biometry for each signature, so the key itself is not treated as a complete same-controller compromise boundary. The API still relies on local-transport restriction, app-server authorization, and structured payload validation.

What changed

  • Added the macOS DeviceKeyProvider implementation backed by Security.framework.
  • Created and loaded P-256 private keys by stable device-key ID.
  • Preferred Secure Enclave keys by default and allowed OS-protected non-extractable fallback only when requested by policy.
  • Returned SPKI DER public keys and DER-encoded ECDSA signatures through the existing crate API.
  • Documented the macOS provider threat-model boundary around this-device-only keys without UserPresence or Biometry flags.
  • Added macOS-only Security.framework and CoreFoundation dependencies.

Stack

This is stacked on #18430.

Validation

  • cargo test -p codex-device-key
  • just bazel-lock-update
  • just bazel-lock-check

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c0fc5bb1ce

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread codex-rs/device-key/src/platform/macos.rs Outdated
@euroelessar euroelessar force-pushed the ruslan/device-key-app-server branch from a6f5f5e to e4e9f03 Compare April 18, 2026 00:05
@euroelessar euroelessar force-pushed the ruslan/device-key-macos branch from c0fc5bb to 4de8c7d Compare April 18, 2026 00:05
@euroelessar euroelessar force-pushed the ruslan/device-key-app-server branch from e4e9f03 to f9220dd Compare April 18, 2026 00:07
@euroelessar euroelessar force-pushed the ruslan/device-key-macos branch 2 times, most recently from cb9c2f2 to e0eb231 Compare April 18, 2026 00:15
@euroelessar euroelessar force-pushed the ruslan/device-key-app-server branch from f9220dd to 75ba150 Compare April 18, 2026 00:15
@euroelessar euroelessar force-pushed the ruslan/device-key-macos branch from e0eb231 to d6970f9 Compare April 18, 2026 00:22
@euroelessar euroelessar force-pushed the ruslan/device-key-app-server branch 2 times, most recently from b59b13f to f7b198b Compare April 18, 2026 00:24
@euroelessar euroelessar force-pushed the ruslan/device-key-macos branch 2 times, most recently from 9ab7c21 to ee62f13 Compare April 18, 2026 00:26
@euroelessar euroelessar force-pushed the ruslan/device-key-app-server branch 2 times, most recently from 54f7030 to c4e3b95 Compare April 18, 2026 00:35
@euroelessar euroelessar force-pushed the ruslan/device-key-macos branch 2 times, most recently from 0060869 to ee406bb Compare April 18, 2026 00:40
@euroelessar euroelessar force-pushed the ruslan/device-key-app-server branch from c4e3b95 to 20c2078 Compare April 18, 2026 00:40
Comment thread codex-rs/device-key/src/platform/macos.rs Outdated
Comment thread codex-rs/device-key/src/platform/macos.rs Outdated
@viyatb-oai

viyatb-oai commented Apr 18, 2026

Copy link
Copy Markdown
Collaborator

[P2] OS‑protected fallback load path doesn’t validate non‑extractable attrs on existing keys; it is tag‑based. If we want the degraded path to still enforce non‑exportable, consider validating key attributes when loading, not just on create.

@euroelessar euroelessar force-pushed the ruslan/device-key-app-server branch from b371887 to f664fa1 Compare April 18, 2026 02:36
@euroelessar euroelessar force-pushed the ruslan/device-key-macos branch from dc066ce to 30d2da0 Compare April 18, 2026 02:36
@euroelessar

Copy link
Copy Markdown
Collaborator Author

[codex] Addressed in 30d2da0: the macOS OS-protected fallback load query now includes kSecAttrIsExtractable = false, matching the create-time non-extractability constraint before an existing key is accepted.

@euroelessar euroelessar marked this pull request as draft April 18, 2026 02:38
@euroelessar euroelessar force-pushed the ruslan/device-key-app-server branch from f664fa1 to 123af8e Compare April 18, 2026 19:25
@euroelessar euroelessar force-pushed the ruslan/device-key-macos branch from e50e78c to 5ff2eec Compare April 23, 2026 21:11
@euroelessar euroelessar changed the base branch from main to ruslan/device-key-sqlite-binding April 23, 2026 21:12
@euroelessar euroelessar force-pushed the ruslan/device-key-sqlite-binding branch from cba35b7 to 05a41cc Compare April 23, 2026 21:39
@euroelessar euroelessar force-pushed the ruslan/device-key-macos branch from 5ff2eec to e38f027 Compare April 23, 2026 21:39
@euroelessar euroelessar force-pushed the ruslan/device-key-sqlite-binding branch from 05a41cc to 3c1f8d4 Compare April 23, 2026 22:02
@euroelessar euroelessar force-pushed the ruslan/device-key-macos branch from e38f027 to 751e183 Compare April 23, 2026 22:02
@euroelessar euroelessar force-pushed the ruslan/device-key-sqlite-binding branch from 3c1f8d4 to 763ee60 Compare April 23, 2026 22:09
euroelessar added a commit that referenced this pull request Apr 23, 2026
## Summary

Load LocalAuthentication.framework at runtime before constructing LAContext instead of linking the framework into every macOS target. The previous direct framework link made unrelated macOS Bazel targets link against an SDK-backed framework path, which caused the macOS CI jobs to fail during linking.

The signing flow still uses LAContext for authenticated private-key operations; this only changes how the framework becomes available to the Objective-C runtime.

## Validation

- just fmt
- cargo test -p codex-device-key
- just fix -p codex-device-key
- git diff --check
- bazel build //codex-rs/device-key:device-key
euroelessar added a commit that referenced this pull request Apr 23, 2026
## Summary

Add the required parameter-name comment for the None authentication-context argument in the macOS device-key lookup path. CI macOS argument-comment lint enforces these comments for positional literal-like arguments.

## Validation

- just fmt
- cargo test -p codex-device-key
- just fix -p codex-device-key
- bazel build --config=argument-comment-lint //codex-rs/device-key:device-key-unit-tests-bin
- git diff --check

Note: a full local just argument-comment-lint run reached the fixed device-key target, then failed later in an unrelated webrtc-sys build script with a stale sandbox path outside this PR changes.
@euroelessar euroelessar force-pushed the ruslan/device-key-macos branch from 751e183 to eab5a71 Compare April 23, 2026 22:10
euroelessar added a commit that referenced this pull request Apr 23, 2026
euroelessar added a commit that referenced this pull request Apr 23, 2026
@euroelessar euroelessar force-pushed the ruslan/device-key-macos branch from f326b6b to aa16360 Compare April 23, 2026 23:07
The device-key crate needs a platform provider that can keep private keys non-extractable on macOS while preserving the crate-level protection policy and structured signing boundary.

macOS has two useful local protection classes for this API: hardware-backed Secure Enclave keys when available, and OS-protected non-extractable keys as an explicit fallback. Reporting which class was selected keeps that distinction visible to callers.

The Security.framework access-control flags used here provide device-local non-exportability and continuity while the user session is unlocked. They do not require UserPresence or Biometry for each signature, so the key itself is not treated as a complete same-controller compromise boundary; the API still relies on local-transport restriction, app-server authorization, and structured payload validation.

- Added the macOS DeviceKeyProvider implementation backed by Security.framework.
- Created and loaded P-256 private keys by stable device-key ID.
- Preferred Secure Enclave keys by default and allowed OS-protected non-extractable fallback only when requested by policy.
- Returned SPKI DER public keys and DER-encoded ECDSA signatures through the existing crate API.
- Documented the macOS provider threat-model boundary around this-device-only keys without UserPresence or Biometry flags.
- Added macOS-only Security.framework and CoreFoundation dependencies.

- just fmt
- cargo test -p codex-device-key
- just fix -p codex-device-key
- git diff --check
- just bazel-lock-update
- just bazel-lock-check
Require macOS-created device keys to prove user presence before private-key use by adding the UserPresence access-control flag at key creation time. This keeps the provider aligned with the device-ownership goal: a valid signature should require both the device-local private key and a successful local biometric or password authentication.

Thread a process-local LAContext into signing lookups and reuse it across sign operations. Security.framework still owns the authentication policy and validity window, while the reusable context lets macOS reuse a recent successful authentication when policy permits instead of prompting on every connection.

Public-key lookup and binding reads continue to avoid the authentication context because they do not use the private key.

- just fmt
- cargo test -p codex-device-key
- just fix -p codex-device-key
- git diff --check
## Summary

Load LocalAuthentication.framework at runtime before constructing LAContext instead of linking the framework into every macOS target. The previous direct framework link made unrelated macOS Bazel targets link against an SDK-backed framework path, which caused the macOS CI jobs to fail during linking.

The signing flow still uses LAContext for authenticated private-key operations; this only changes how the framework becomes available to the Objective-C runtime.

## Validation

- just fmt
- cargo test -p codex-device-key
- just fix -p codex-device-key
- git diff --check
- bazel build //codex-rs/device-key:device-key
## Summary

Add the required parameter-name comment for the None authentication-context argument in the macOS device-key lookup path. CI macOS argument-comment lint enforces these comments for positional literal-like arguments.

## Validation

- just fmt
- cargo test -p codex-device-key
- just fix -p codex-device-key
- bazel build --config=argument-comment-lint //codex-rs/device-key:device-key-unit-tests-bin
- git diff --check

Note: a full local just argument-comment-lint run reached the fixed device-key target, then failed later in an unrelated webrtc-sys build script with a stale sandbox path outside this PR changes.
@euroelessar

Copy link
Copy Markdown
Collaborator Author

Update pushed in two commits:

  • 21ab239bc5 adds kSecUseDataProtectionKeychain to the macOS private-key query/create attributes.
  • eed4fccd03 adds codex-rs/scripts/check-device-key-app-server.py, a repeatable local harness that spawns codex app-server --listen stdio:// and exercises device/key/create, device/key/sign, and device/key/public.

Local check against the OpenAI-signed debug CLI currently reproduces the remaining blocker at device/key/create:

{
  "failedMethod": "device/key/create",
  "error": {
    "code": -32603,
    "message": "device key platform error: macOS Keychain entitlements are required to create persistent device keys"
  }
}

Command used:

codex-rs/scripts/check-device-key-app-server.py --binary codex-rs/target/debug/codex

@github-actions

github-actions Bot commented May 9, 2026

Copy link
Copy Markdown
Contributor

Closing this pull request because it has had no updates for more than 14 days. If you plan to continue working on it, feel free to reopen or open a new PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants