Skip to content

Add support to reopen the commissioning window #34

@p0fi

Description

@p0fi

Context

Matter supports a multi-admin model: a device commissioned into one fabric (e.g. ours) can be handed off to another controller (Apple Home, Google Home, SmartThings) without factory-resetting. The current Administrator re-opens a one-shot commissioning window on the already-commissioned Node, prints a QR / Manual Pairing Code, and the user enters it into the new controller.

Today matter-cli can only commission new devices — there is no command to open a fresh window on an existing node.

Spec references:

  • connectedhomeip-spec/src/multi_admin/multi_admin.adoc (Ch.12 Multiple Administrators)
  • connectedhomeip-spec/src/service_device_management/AdminCommissioningCluster.adoc (cluster 0x003C)
  • connectedhomeip-spec/src/secure_channel/Password_Authenticated_Session_Establishment.adoc (PAKE verifier derivation)

Current State in the Codebase

Most of the primitives already exist — this issue mostly wires them together:

Need Where it lives today
AdministratorCommissioning cluster (0x003C) TLV types & command IDs internal/clusters/administratorcommissioning/cluster.go (codegen)
DeriveSPAKE2PValues(passcode, salt, iterations)w0, w1 internal/crypto/pbkdf2.go
ComputeL(w1) → 65-byte uncompressed P-256 point internal/crypto/spake2p.go
Timed Invoke over CASE interaction.Client.InvokeTimed (internal/interaction/client.go:151) + daemon path via InvokeReq.TimedMs (internal/daemon/server.go:460)
QR / Manual Pairing Code generation from passcode + discriminator commissioning.SetupPayload.QRCode / .ManualPairingCode (internal/commissioning/payload.go) — already used by matter code generate
Stored VID / PID per node (needed so the generated QR is accepted by commercial ecosystems) store.Node.VendorID / .ProductID, populated by buildNodeFromResult in cli/commission.go:350
Cobra wiring pattern with @target + daemon-aware connect cli/cluster.go:528 invokeCommand

So the missing pieces are: a command, the PAKE verifier construction (w0 || L, 97 bytes), and the OCW-specific UX (random passcode, countdown, close/status).

Proposed CLI

# Open an Enhanced Commissioning Window on an already-commissioned node.
# Default timeout 180s, random 27-bit passcode + 12-bit discriminator.
matter commission open-window @1
matter commission open-window @kitchen-light --timeout 5m

# Power-user flags (mostly for testing / reproducibility).
matter commission open-window @1 \
    --passcode 20202021 \
    --discriminator 3840 \
    --iterations 10000      # default 1000; spec allows 1000..100000

# Close an open window early.
matter commission close-window @1

# Basic Commissioning Method (only if device advertises Feature BC — rare).
matter commission open-window @1 --basic

Expected output (happy path)

✓ Reading BasicInformation (VID=0xFFF1, PID=0x8000)
✓ Opening commissioning window on node 1 (timeout: 3m0s, iterations: 1000)
✓ Commissioning window is open

  QR Code:      MT:Y3.13OTB00KA0648G00
  Manual Code:  3497-011-2332
  Passcode:     20202021
  Discriminator: 3840
  Expires:      15:42:17 (in 2m58s)

Pair this device into another ecosystem by scanning the QR code above,
or enter the manual code into the target controller (Apple Home, Google Home, …).

On close:

✓ Revoking commissioning window on node 1
✓ Window closed

Implementation Tasks

  1. New file cli/commission_window.go with three subcommands under commission:

    • open-window (Enhanced Commissioning Method, command 0x00)
    • close-window (RevokeCommissioning, command 0x02)
    • optional: window-status — trivial read of AdministratorCommissioning.WindowStatus / AdminFabricIndex / AdminVendorId on endpoint 0 (can be done with matter cluster read today, so may skip)
  2. PAKE verifier helper — new internal/commissioning/verifier.go (or add to existing payload.go):

    // ComputePAKEVerifier returns the 97-byte (w0 || L) verifier for OCW.
    func ComputePAKEVerifier(passcode uint32, salt []byte, iterations int) ([]byte, error)

    Implementation: DeriveSPAKE2PValuesComputeL(w1)append(w0, L...). Add table-driven tests with vectors from connectedhomeip/src/crypto/tests/ (the SDK ships spake2p test vectors).

  3. Random salt + passcode generators (crypto/rand):

    • Passcode: 27-bit uniform, reject the 12 invalid values listed in the spec (00000000, 11111111, 22222222, …, 12345678, 87654321).
    • Salt: 16 random bytes (spec constraint 16..32).
    • Discriminator: 12-bit random when not supplied.
  4. Open-window command flow (runOpenWindow):

    1. Resolve @target → node ID; require it (error if missing).
    2. Load store.Node → pull VendorID / ProductID. If zero, fall back to reading BasicInformation cluster over CASE.
    3. Generate passcode/salt/discriminator (or take from flags).
    4. Compute PAKE verifier.
    5. TLV-encode administratorcommissioning.OpenCommissioningWindowRequest.
    6. Timed Invoke on endpoint 0, cluster 0x003C, command 0x00, with timedMs equal to the IM-level timeout (spec recommends e.g. 10 * 1000 ms, independent of commissioning timeout). Prefer daemon path if running, else direct CASE.
    7. Build a commissioning.SetupPayload from {VID, PID, passcode, discriminator, flow=Standard, DiscoveryCapabilities=OnNetwork} and call .QRCode() + .ManualPairingCode().
    8. Print result + expiry time.
  5. Close-window command flow (runCloseWindow):

    • Timed Invoke of RevokeCommissioning (command 0x02, no fields) on cluster 0x003C, endpoint 0. Map the WindowNotOpen cluster-specific status (0x04) to a friendly error.
  6. Cluster-specific status decoding — the Admin Commissioning cluster returns cluster-specific status codes (0x02 Busy, 0x03 PAKEParameterError, 0x04 WindowNotOpen). Add a small mapper in the new file and surface a human message instead of raw 0x02.

  7. Flags

    --timeout duration    (default 3m, range 3m..15m — spec: 180..900 s)
    --iterations uint32   (default 1000, range 1000..100000)
    --passcode uint32     (optional; random if unset)
    --discriminator uint16 (optional; random if unset)
    --salt-hex string     (optional; 16 random bytes if unset)
    --basic               (use OpenBasicCommissioningWindow instead; no PAKE params)
    
  8. Help / CLAUDE.md — add examples under "Core commands" in CLAUDE.md and refresh matter-cli help so the new subcommands are visible.

  9. Tests

    • Unit: ComputePAKEVerifier against SDK vectors; flag parsing & validation; cluster-status mapping.
    • Integration (build-tag integration): against matter.js test device (there is a matter-js-test-device skill). Open → inspect WindowStatus attr == WindowOpen → close → WindowStatus == WindowNotOpen.

Acceptance Criteria

  • matter commission open-window @<node> prints a working QR + manual code and WindowStatus on the device transitions to WindowOpen.
  • A second controller (matter.js test device commissioner, chip-tool, or a real ecosystem) can pair against the node using the printed code, entering a second fabric.
  • matter commission close-window @<node> revokes an open window; WindowStatus returns to WindowNotOpen.
  • Calling open-window twice in a row returns a clean "commissioning window already open (Busy)" error, not a raw 0x02.
  • Timed-invoke path is used (non-timed invoke of OCW is rejected by the spec).
  • Works both standalone and with the session daemon running (-K 30m).
  • Documented in CLAUDE.md under Core commands and shown in matter commission --help.

Out of Scope / Follow-ups

  • JointFabricAdministrator cluster (0x0753) — separate cluster for joint-fabric handoff; not needed for the common "add to Apple Home" case. Track separately.
  • Rendering the QR as an actual scannable image/ANSI block in the terminal — issue #? (nice-to-have; printing the MT: string is sufficient for every major ecosystem app).
  • Automatic decommission if the new admin does not complete — belongs in Proper device decommissioning with matter decommission #46 (decommission).

Metadata

Metadata

Assignees

No one assigned

    Labels

    cliCobra commands, flags, help, output formattingcommissioningPASE/CASE, BLE, on-network, attestationfeatureWholly new capability

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions