Skip to content

feat(mdm): add supervised passcode and Screen Time management commands#778

Open
aluedeke wants to merge 6 commits into
mainfrom
feature/passcode-unlock-token
Open

feat(mdm): add supervised passcode and Screen Time management commands#778
aluedeke wants to merge 6 commits into
mainfrom
feature/passcode-unlock-token

Conversation

@aluedeke

Copy link
Copy Markdown
Collaborator

Summary

Adds three new subcommands under ios mdm for supervised device passcode management via the MCInstall escalation channel:

  • ios mdm fetch-unlock-token — saves the device passcode unlock token to a file; must be run while the device has no passcode set (typically during provisioning)
  • ios mdm clear-passcode — removes the device lock passcode using a previously saved unlock token; works regardless of current lock state and without knowing the passcode
  • ios mdm clear-screen-time-password — clears the Screen Time restrictions PIN; no token required, supervisor identity alone suffices

All three commands require a supervised device and a PKCS#12 supervisor identity (--p12file + --password or P12_PASSWORD env var).

Protocol background

The device lock passcode flow is a two-step MCInstall protocol (discovered by reverse engineering MobileDeviceKit.framework from Apple Configurator 2):

  1. RequestUnlockToken → device returns a ~1300-byte keybag escrow blob (only works when no passcode is set)
  2. ClearPasscode + UnlockToken → clears the passcode (works even when device is locked)

The Screen Time command is a single MCInstall ClearRestrictionsPassword request with no additional token required.

New MCInstall methods

  • FetchUnlockToken() ([]byte, error) — on an already-escalated connection
  • FetchUnlockTokenSupervised(p12bytes, password) — escalates then fetches
  • ClearPasscodeSupervised(p12bytes, password, token) — escalates then clears
  • ClearScreenTimePasswordSupervised(p12bytes, password) — escalates then clears Screen Time PIN

Test plan

  • ios mdm fetch-unlock-token --p12file=<file> --password=<pw> --output=/tmp/token.bin on a passcode-free supervised device → file written, JSON {"bytes":1304,"path":"..."} printed
  • Set a passcode on the device, then ios mdm clear-passcode --p12file=<file> --password=<pw> --token=/tmp/token.bin{"status":"ok"}, passcode gone
  • ios mdm fetch-unlock-token on a device with passcode set → exits 1 with DMCKeybagErrorDomain error
  • ios mdm clear-screen-time-password on a device with Screen Time PIN → {"status":"ok"}, PIN cleared

🤖 Generated with Claude Code

aluedeke and others added 2 commits June 23, 2026 13:49
Three new supervised commands under `ios mdm`:
- fetch-unlock-token: saves the device passcode unlock token to a file
  (device must have no passcode set; run once during provisioning)
- clear-passcode: removes the device lock passcode using a saved token
- clear-screen-time-password: clears the Screen Time restrictions PIN
  (no token required; supervisor identity alone suffices)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
fetch-unlock-token --output=- prints the token as base64 to stdout
instead of writing a file, making it easy to pipe into a secrets manager.
clear-passcode --token=- reads a base64-encoded token from stdin.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
aluedeke and others added 4 commits June 23, 2026 14:08
Drop *Supervised wrappers. Library methods (FetchUnlockToken, ClearPasscode,
ClearScreenTimePassword) now operate on an already-escalated connection.
CLI handler escalates once upfront and calls the clean API methods.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
EscrowBag is absent from the pair response when the device is locked,
causing a nil interface assertion panic. Use comma-ok and treat a missing
EscrowBag as non-fatal — the existing pair record in usbmuxd remains
valid and passcode clearing uses the separately stored unlock token.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When the device has a passcode, it accepts the supervisor identity but
cannot update its trust store or return a new EscrowBag. Saving a new
pair record (with freshly generated certificates) corrupts the existing
valid record in usbmuxd — subsequent operations fail because the device
still expects the original provisioning certificates.

Skip savePair when EscrowBag is absent; the existing pair record remains
valid for MCInstall and all other services.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Previously returned cleanly (exit 0) on mount error, silently bypassing
set -e in launch.sh and letting the pod continue with no testmanagerd.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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.

1 participant