Skip to content

inject: embedder-held CryptoKeys as typed key handles (#391) - #390

Closed
lannbot wants to merge 1 commit into
mainfrom
feat/persisted-keys
Closed

inject: embedder-held CryptoKeys as typed key handles (#391)#390
lannbot wants to merge 1 commit into
mainfrom
feat/persisted-keys

Conversation

@lannbot

@lannbot lannbot commented Aug 22, 2026

Copy link
Copy Markdown
Contributor

Reworked in place per #391's ruling (the pivot commit's history records both shapes; #389 closed superseded, #97's NOT_PLANNED honored throughout).

What fell: the polymorph:webcrypto-keystore WIT package from the first commit. Store-a-handle is browser-specific — WebCrypto §13 structured clone is the only mechanism anywhere for persisting a live handle with its policy intact; TPM/SE/CNG mint inside the store and refuse import — so a WIT contract over that spread promises a shape whose substance varies by embedding (#128's test, failed at embedding granularity). The IndexedDB persistence library moves to the consumer (polyvisor's device store absorbs it).

What ships — the one piece only this package can provide (#391): webcryptoHost() → { imports, inject } on the polyengine host module. imports is exactly webcryptoImports()'s record (which remains, unchanged — consumers spread it). inject.signingKey(key) wraps an Ed25519 private key as a signature.signing-key handle; inject.derivationKey(key) lands an HKDF/PBKDF2 secret on the base-secret resource its algorithm names (hkdf.ikm / pbkdf2.password — the derivation kind has no single key resource by design). No WIT change anywhere.

Contract points held: kind validated at the wrap (Ed25519 only for signing — ECDSA/RSA bind digest/salt at mint and WebCrypto carries neither on the key, so a wrapped handle would have to invent what its getters must report); policy reported, never enforced — injected handles carry no mint record and the #86/#128 getters answer from the key's own [[algorithm]]/[[usages]]/[[extractable]], verified in the probe both directions. One honest note: the {imports, inject} pairing is a documented contract rather than an enforced boundary — this module's resource classes are singletons and the runtime allocates reps lazily at the crossing; a brand check in every resource method would be enforcement theater for a misuse that currently cannot go wrong.

Gates: validate-wit green (without the package); polyengine-module-check 17/17; new polyengine-inject-probe 12/12 in real Chromium (sign+verify through an injected non-extractable key, truthful getters both extractability directions, public-key/ECDSA/AES rejections, minted+injected coexistence in one invocation, HKDF and PBKDF2 injects byte-identical to the platform's own deriveBits); publish dry-run clean; conformance failure sets byte-identical to the stashed baseline (the declared #351 debt).

Known gap, recorded: the table crossing itself (injected instance through repFor/lookup in a live guest graph) is exercised first by polyvisor's integration — driving a real guest here would need an app-owned WIT world this package deliberately does not define.

Version: purely additive host surface (webcryptoHost, two exported types; the never-released ./keystore export removed) → 0.3.1 on the next release commit; ^0.3.0 consumers pick it up without a manifest edit.

…ystore 0.1.0)

The persisted-key slice (#389; closed-#97's sibling-package ruling
honored): a NEW WIT package rather than an extension to
polymorph:webcrypto, which tracks the WebCrypto standard and takes no
extensions. signing-keystore stores a signing-key HANDLE under a
guest-chosen name inside an embedder-chosen storage root; no key
material crosses the interface in either direction, so a
non-extractable key stays non-extractable forever — the property the
alternative (export + re-import) cannot carry.

Host module js/polyengine/src/keystore.ts, exported as
@polymorph/webcrypto/keystore: one IndexedDB database per namespace,
CryptoKey handles via structured clone (WebCrypto section-13 steps).
Both edges enforce extractability: persist refuses an extractable key;
load re-validates algorithm/type/usages/extractable===false and
DISCARDS a failing entry (IndexedDB is origin-writable, so a stored
entry is untrusted input on the way back in — wosh identity-store's
validated predicate). The mint-bound algorithm record is rebuilt from
the module constant, never read from storage. Ed25519 only in v1, per
the #389 kind-coverage ruling.

Gated by a new Playwright browser probe (Deno has no IndexedDB):
persist -> real page reload -> load -> sign verified against the
pre-reload public half, both extractability edges, planted-entry
discards, namespace isolation, no-keystore refusal — 10/10. WIT
validation covers the new package; publish dry-run clean; conformance
failure sets byte-identical to baseline (the declared #351 debt).

First consumer: polyvisor's G5 device store (platform-posture identity
resume; its runtime/PERSISTENCE.md T-A). Additive surface: 0.3.1 on
the next release commit.
@lann lann closed this Aug 22, 2026
@lannbot lannbot changed the title keystore: signing keys survive instantiations (polymorph:webcrypto-keystore 0.1.0) inject: embedder-held CryptoKeys as typed key handles (#391) Aug 22, 2026
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