Skip to content

fido2 derive: X-Wing (mlkem768x25519) split-custody support [UNTESTED]#30

Open
0c-coder wants to merge 1 commit into
trustcrypto:nemoclaw-openclaw-security-layerfrom
0c-coder:feature/xwing-derive-webapp
Open

fido2 derive: X-Wing (mlkem768x25519) split-custody support [UNTESTED]#30
0c-coder wants to merge 1 commit into
trustcrypto:nemoclaw-openclaw-security-layerfrom
0c-coder:feature/xwing-derive-webapp

Conversation

@0c-coder

@0c-coder 0c-coder commented Jul 1, 2026

Copy link
Copy Markdown

FIDO2 derive: X-Wing (mlkem768x25519) split-custody support — UNTESTED

⚠️ Not built or tested on hardware. By-inspection only; mirrors the
existing derive/button-press/send_transport_response patterns. Please review
and validate on a device before merging.

Adds an X-Wing branch to the FIDO2 keyhandle derive flow (bridge_to_onlykey,
fido2/ok_extension.cpp) so the OnlyKey web app can do post-quantum age
(mlkem768x25519) with no large payloads over FIDO2.

Model (split custody)

The device keeps the X25519 half; the browser does the ML-KEM half:

  • sk_X (X25519) is derived per label from the web-derivation key and never
    leaves
    — the device only returns ss_X = X25519(sk_X, ct_X).
  • The device also returns a 32-byte mlkem_seed; the browser expands it and
    decapsulates the 1088-byte ML-KEM ciphertext locally (never sent to the
    device). Decryption still requires the OnlyKey (no ss_X without it).

Every round-trip is ≤ 64 bytes. Full spec + browser side + a passing crypto
proof: onlykey.github.io PR #39 (src/plugins/age/INTEGRATION.md,
test/xwing-split.test.mjs).

Wire

  • keytype: wire byte 5KEYTYPE_XWING (6) after the existing opt2++.
  • DERIVE_PUBLIC_KEY[ pk_X(32) | mlkem_seed(32) ]
  • DERIVE_SHAREDSEC(_REQ_PRESS)[ ss_X(32) | mlkem_seed(32) ] (button press)
  • Response encrypted under the OKCONNECT transit_key when ENCRYPT_RESP.

Derivation (domain-separated — the security-critical bit)

  • sk_X = existing okcrypto_derive_key(KEYTYPE_CURVE25519, additional_data, RESERVED_KEY_WEB_DERIVATION).
  • mlkem_seed = SHA256( sk_X || "onlykey/xwing/mlkem768-seed/v1" ) — one-way, so
    handing mlkem_seed to the browser can never disclose sk_X, and it is
    constant per label (stable recipient).

Review notes / open items

  • Windows-1903 duplicate-request handling (the os=='W' dedupe used by the
    classical DERIVE_SHAREDSEC path) is not replicated here — confirm whether
    PQC decrypt needs it.
  • send_transport_response length is 32 + sizeof(UNLOCKED) + 1 + 64; verify
    the transit-encryption path handles the 64-byte body.
  • No slot/keytype opt2 collisions with existing classical types (5/6 are new).

Web app PQC over the existing derive flow with no large payloads: device keeps
sk_X (X25519) and returns [pk_X|mlkem_seed] or [ss_X|mlkem_seed] (64 bytes);
browser expands mlkem_seed and does the ML-KEM half locally. mlkem_seed =
SHA256(sk_X || domain-tag), one-way/domain-separated. Not hardware-tested.
@0c-coder

0c-coder commented Jul 1, 2026

Copy link
Copy Markdown
Author

Web-app counterpart (spec + browser impl + passing crypto proof): onlykey/onlykey.github.io#39. Together they deliver split-custody X-Wing: device does X25519, browser does ML-KEM, no large FIDO2 payloads.

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.

2 participants