@interncom/diplomatic 0.11.4 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.mjs +2 -2
- package/dist/cli/shared/binary.d.ts +4 -0
- package/dist/cli/shared/codecs/bundleHost.d.ts +11 -0
- package/dist/cli/shared/codecs/identityBundle.d.ts +25 -0
- package/dist/cli/shared/codecs/pairPackage.d.ts +15 -0
- package/dist/cli/shared/crypto/enclave.d.ts +113 -2
- package/dist/cli/shared/crypto/entropy.d.ts +11 -0
- package/dist/cli/shared/seed.d.ts +25 -0
- package/dist/cli/shared/types.d.ts +4 -6
- package/dist/cli/shared/webauthn/common.d.ts +38 -0
- package/dist/cli/shared/webauthn/largeBlob.d.ts +19 -0
- package/dist/cli/shared/webauthn/prf.d.ts +33 -0
- package/dist/cli/shared/worker/embeddedWorkerSource.d.ts +1 -0
- package/dist/cli/shared/worker/spawn.d.ts +5 -0
- package/dist/cli/src/index.d.ts +10 -8
- package/dist/web/client.d.ts +3 -2
- package/dist/web/identity/pairPackage.d.ts +31 -0
- package/dist/web/index.d.ts +18 -6
- package/dist/web/index.mjs +1 -1
- package/dist/web/openClient.d.ts +14 -109
- package/dist/web/passkey/largeBlob.d.ts +17 -0
- package/dist/web/passkey/prf-store.d.ts +62 -0
- package/dist/web/passkey/seed.d.ts +13 -99
- package/dist/web/passkey/webauthn.d.ts +1 -0
- package/dist/web/react/useClient.d.ts +13 -20
- package/dist/web/shared/binary.d.ts +4 -0
- package/dist/web/shared/codecs/bundleHost.d.ts +11 -0
- package/dist/web/shared/codecs/identityBundle.d.ts +25 -0
- package/dist/web/shared/codecs/pairPackage.d.ts +15 -0
- package/dist/web/shared/codecs/pairPackageEnvelope.d.ts +18 -0
- package/dist/web/shared/crypto/enclave.d.ts +113 -2
- package/dist/web/shared/crypto/entropy.d.ts +11 -0
- package/dist/web/shared/seed.d.ts +25 -0
- package/dist/web/shared/types.d.ts +4 -6
- package/dist/web/shared/webauthn/common.d.ts +38 -0
- package/dist/web/shared/webauthn/largeBlob.d.ts +19 -0
- package/dist/web/shared/webauthn/prf.d.ts +33 -0
- package/dist/web/shared/worker/embeddedWorkerSource.d.ts +1 -0
- package/dist/web/shared/worker/spawn.d.ts +5 -0
- package/dist/web/stores/idb/seed.d.ts +24 -6
- package/dist/web/stores/memory/seed.d.ts +3 -4
- package/dist/web/types.d.ts +13 -7
- package/dist/web/worker/client.d.ts +26 -48
- package/dist/web/worker.mjs +1 -1
- package/package.json +1 -5
package/dist/web/openClient.d.ts
CHANGED
|
@@ -1,103 +1,10 @@
|
|
|
1
1
|
import { IClock } from "./shared/clock";
|
|
2
2
|
import type { IStateManager } from "./shared/types";
|
|
3
3
|
import type { IClient, IStore } from "./types";
|
|
4
|
-
/**
|
|
5
|
-
* How to construct the Worker you pass to {@link openDiplomaticClient} /
|
|
6
|
-
* {@link WorkerClient.connect} / `useClient`.
|
|
7
|
-
*
|
|
8
|
-
* The library accepts only a live `Worker` instance. Instantiation is bundler-
|
|
9
|
-
* and hosting-specific — the app owns that step so DIPLOMATIC never guesses a
|
|
10
|
-
* script URL that would 404 under a different tool.
|
|
11
|
-
*
|
|
12
|
-
* The worker entry is the package export `@interncom/diplomatic/worker`
|
|
13
|
-
* (built as `worker.mjs` in the published package). It must run as a
|
|
14
|
-
* **module** worker (`type: "module"`).
|
|
15
|
-
*
|
|
16
|
-
* ## Handshake (race-safe)
|
|
17
|
-
*
|
|
18
|
-
* The worker posts an unsolicited `{ kind: "ready" }` after init. Apps often
|
|
19
|
-
* construct the Worker at module load (or before `openDiplomaticClient` finishes
|
|
20
|
-
* opening IndexedDB), so that event can fire before the library sets
|
|
21
|
-
* `onmessage`. **That is fine:** connect also probes with a request/response
|
|
22
|
-
* `ping`. The worker holds commands until init completes, so the probe succeeds
|
|
23
|
-
* even if `ready` was dropped. Do **not** reimplement message buffering in the
|
|
24
|
-
* app unless you need it for other reasons.
|
|
25
|
-
*
|
|
26
|
-
* ## Vite (recommended for SPA templates)
|
|
27
|
-
*
|
|
28
|
-
* ```ts
|
|
29
|
-
* import DiplomaticWorker from "@interncom/diplomatic/worker?worker";
|
|
30
|
-
* const worker = new DiplomaticWorker();
|
|
31
|
-
* await openDiplomaticClient({ state, worker });
|
|
32
|
-
* ```
|
|
33
|
-
*
|
|
34
|
-
* `?worker` makes Vite emit a real worker asset and a constructor. Create the
|
|
35
|
-
* instance once (module scope or `useMemo`/`useRef`) — not on every render.
|
|
36
|
-
* Early construction is supported; the handshake recovers if `ready` is missed.
|
|
37
|
-
*
|
|
38
|
-
* ## webpack / Rollup / esbuild / Parcel (`new URL` + import.meta.url)
|
|
39
|
-
*
|
|
40
|
-
* Point at the package worker file so the bundler copies it next to the app:
|
|
41
|
-
*
|
|
42
|
-
* ```ts
|
|
43
|
-
* const worker = new Worker(
|
|
44
|
-
* new URL("@interncom/diplomatic/worker", import.meta.url),
|
|
45
|
-
* { type: "module" },
|
|
46
|
-
* );
|
|
47
|
-
* ```
|
|
48
|
-
*
|
|
49
|
-
* If the package path does not resolve, import the built file explicitly:
|
|
50
|
-
*
|
|
51
|
-
* ```ts
|
|
52
|
-
* const worker = new Worker(
|
|
53
|
-
* new URL(
|
|
54
|
-
* "../node_modules/@interncom/diplomatic/dist/worker.mjs",
|
|
55
|
-
* import.meta.url,
|
|
56
|
-
* ),
|
|
57
|
-
* { type: "module" },
|
|
58
|
-
* );
|
|
59
|
-
* ```
|
|
60
|
-
*
|
|
61
|
-
* (Exact relative path depends on your app layout; prefer the package export
|
|
62
|
-
* when your bundler supports it.)
|
|
63
|
-
*
|
|
64
|
-
* ## Plain HTML / CDN / static hosting (no bundler)
|
|
65
|
-
*
|
|
66
|
-
* Host `worker.mjs` (from the package `dist/`) as a same-origin static asset,
|
|
67
|
-
* then:
|
|
68
|
-
*
|
|
69
|
-
* ```html
|
|
70
|
-
* <script type="module">
|
|
71
|
-
* import { openDiplomaticClient } from "https://cdn.example/diplomatic.js";
|
|
72
|
-
* const worker = new Worker("/path/to/worker.mjs", { type: "module" });
|
|
73
|
-
* const { client } = await openDiplomaticClient({ state, worker });
|
|
74
|
-
* </script>
|
|
75
|
-
* ```
|
|
76
|
-
*
|
|
77
|
-
* The worker script must be **same-origin** (or CORS-enabled for classic
|
|
78
|
-
* workers; module workers generally need same-origin). Do not invent a path
|
|
79
|
-
* into `node_modules` from the browser — copy or serve the built file.
|
|
80
|
-
*
|
|
81
|
-
* ## React (`useClient`)
|
|
82
|
-
*
|
|
83
|
-
* Same rules: build the Worker once, pass the instance:
|
|
84
|
-
*
|
|
85
|
-
* ```ts
|
|
86
|
-
* import DiplomaticWorker from "@interncom/diplomatic/worker?worker";
|
|
87
|
-
* const syncWorker = new DiplomaticWorker(); // module scope
|
|
88
|
-
* useClient({ seed, host, worker: syncWorker });
|
|
89
|
-
* ```
|
|
90
|
-
*
|
|
91
|
-
* ## Lifecycle
|
|
92
|
-
*
|
|
93
|
-
* Workers die with the page (tab close / full navigation). Mid-session death
|
|
94
|
-
* is rare; if you recreate a Worker yourself, open a new client against it.
|
|
95
|
-
* `dispose` / `WorkerClient.terminate` stops the worker DIPLOMATIC is using.
|
|
96
|
-
*/
|
|
97
4
|
type OpenDiplomaticClientBase = {
|
|
98
5
|
state: IStateManager;
|
|
99
6
|
clock?: IClock;
|
|
100
|
-
/** Max wait for
|
|
7
|
+
/** Max wait for setSeed → Enclave.spawnSyncWorker handshake (default 15s). */
|
|
101
8
|
readyTimeoutMs?: number;
|
|
102
9
|
/**
|
|
103
10
|
* Debounce local write → worker sync (default `defaultSyncDebounceMs`).
|
|
@@ -106,29 +13,29 @@ type OpenDiplomaticClientBase = {
|
|
|
106
13
|
syncDebounceMs?: number;
|
|
107
14
|
};
|
|
108
15
|
/**
|
|
109
|
-
* Worker path: protocol sync off the main thread. Main and worker
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
16
|
+
* Worker path: protocol sync off the main thread after setSeed. Main and worker
|
|
17
|
+
* both use IndexedDB. Custom `store` is forbidden (worker always opens IDB).
|
|
18
|
+
*
|
|
19
|
+
* No worker is spawned at open. Only {@link Enclave.spawnSyncWorker} (from
|
|
20
|
+
* setSeed) creates a worker and injects seed.
|
|
21
|
+
*
|
|
22
|
+
* CSP: allow `worker-src blob:` (or `script-src blob:`) if you use a strict CSP.
|
|
113
23
|
*/
|
|
114
24
|
export type OpenDiplomaticClientWorkerOptions = OpenDiplomaticClientBase & {
|
|
115
|
-
/**
|
|
116
|
-
|
|
117
|
-
* Misconfiguration or ready timeout throws — no silent main-thread fallback.
|
|
118
|
-
*/
|
|
119
|
-
worker: Worker;
|
|
25
|
+
/** Request a library-managed sync worker. */
|
|
26
|
+
worker: true;
|
|
120
27
|
store?: never;
|
|
121
28
|
};
|
|
122
29
|
/**
|
|
123
30
|
* Main-thread path: SyncClient on the page. Optional custom store; default is
|
|
124
|
-
* IndexedDB.
|
|
31
|
+
* IndexedDB.
|
|
125
32
|
*/
|
|
126
33
|
export type OpenDiplomaticClientMainOptions = OpenDiplomaticClientBase & {
|
|
127
|
-
worker?: undefined;
|
|
34
|
+
worker?: undefined | false;
|
|
128
35
|
/**
|
|
129
36
|
* Protocol message store. Default: IndexedDB (`openIDBStore`).
|
|
130
37
|
* Pass explicitly for non-IDB backends (e.g. `new MemoryStore(crypto)`).
|
|
131
|
-
* Incompatible with `worker
|
|
38
|
+
* Incompatible with `worker: true`.
|
|
132
39
|
*/
|
|
133
40
|
store?: IStore<URL>;
|
|
134
41
|
};
|
|
@@ -143,10 +50,8 @@ export type OpenedDiplomaticClient = {
|
|
|
143
50
|
/**
|
|
144
51
|
* Open a browser client.
|
|
145
52
|
*
|
|
146
|
-
* - **Worker path:**
|
|
53
|
+
* - **Worker path:** `worker: true` — façade ready; Worker appears on setSeed.
|
|
147
54
|
* - **Main path:** omit `worker` → SyncClient on the page; optional `store`.
|
|
148
|
-
*
|
|
149
|
-
* See {@link OpenDiplomaticClientOptions} for Worker instantiation recipes.
|
|
150
55
|
*/
|
|
151
56
|
export declare function openDiplomaticClient(opts: OpenDiplomaticClientOptions): Promise<OpenedDiplomaticClient>;
|
|
152
57
|
export {};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Status } from "../shared/consts";
|
|
2
|
+
import { type LargeBlobCreateOpts, type LargeBlobRp } from "../shared/webauthn/largeBlob";
|
|
3
|
+
import { type ValStat } from "../shared/valstat";
|
|
4
|
+
export type { LargeBlobCreateOpts, LargeBlobRp };
|
|
5
|
+
/**
|
|
6
|
+
* WebAuthn largeBlob byte I/O for non-seed opaque data.
|
|
7
|
+
* For seed/identity use {@link Enclave} largeBlob methods only.
|
|
8
|
+
*/
|
|
9
|
+
export declare const LargeBlob: {
|
|
10
|
+
readonly capable: () => Promise<boolean>;
|
|
11
|
+
readonly createCred: (opts?: LargeBlobCreateOpts) => Promise<ValStat<Uint8Array>>;
|
|
12
|
+
/**
|
|
13
|
+
* Write arbitrary opaque bytes (UV). Do not pass unencrypted seed from app code.
|
|
14
|
+
*/
|
|
15
|
+
readonly write: (credId: Uint8Array, data: Uint8Array, opts?: LargeBlobRp) => Promise<Status>;
|
|
16
|
+
readonly read: (credId: Uint8Array, opts?: LargeBlobRp) => Promise<ValStat<Uint8Array>>;
|
|
17
|
+
};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { Enclave } from "../shared/crypto/enclave";
|
|
2
|
+
import { type SealedMasterKey } from "../shared/seed";
|
|
3
|
+
import type { ICrypto } from "../shared/types";
|
|
4
|
+
import { type ValStat } from "../shared/valstat";
|
|
5
|
+
import type { ISeedStore, SetSeedOpts } from "../types";
|
|
6
|
+
import type { PrfRp } from "../shared/webauthn/prf";
|
|
7
|
+
export type PrfSeedMeta = {
|
|
8
|
+
/** AEAD-sealed master under KDF(PRF). */
|
|
9
|
+
sealedMaster: SealedMasterKey;
|
|
10
|
+
/** PRF salt used at wrap time. */
|
|
11
|
+
salt: Uint8Array;
|
|
12
|
+
/** Optional allowCredentials id. */
|
|
13
|
+
credId?: Uint8Array;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Persist sealed-master metadata for cold start (e.g. protocol IDB seedMeta).
|
|
17
|
+
* Called with `undefined` when the store is wiped.
|
|
18
|
+
*/
|
|
19
|
+
export type PersistPrfSeedMeta = (meta: PrfSeedMeta | undefined) => void | Promise<void>;
|
|
20
|
+
export type PrfSeedStoreOpts = PrfRp & {
|
|
21
|
+
crypto: ICrypto;
|
|
22
|
+
/**
|
|
23
|
+
* Required durable write path for sealed master + salt + credId.
|
|
24
|
+
* Protocol IDB implements this via {@link IDBSeedStore.persistPrfMeta}.
|
|
25
|
+
*/
|
|
26
|
+
persistMeta: PersistPrfSeedMeta;
|
|
27
|
+
/** Previously persisted meta (cold start). Does not re-call persistMeta. */
|
|
28
|
+
meta?: PrfSeedMeta;
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Session + durable PRF-wrapped seed. Session secret is always {@link Enclave}.
|
|
32
|
+
*
|
|
33
|
+
* - {@link save} is memory-only (holds enclave).
|
|
34
|
+
* - {@link wrapAndSave} runs PRF UV inside Enclave and persists sealed meta.
|
|
35
|
+
* - {@link unlock} runs PRF UV inside Enclave and returns a new enclave.
|
|
36
|
+
*
|
|
37
|
+
* Fallback when PRF is unavailable (not implemented yet): passphrase-sealed
|
|
38
|
+
* meta with the same layout — no plain seed on disk either way.
|
|
39
|
+
*/
|
|
40
|
+
export declare class PrfSeedStore implements ISeedStore {
|
|
41
|
+
#private;
|
|
42
|
+
constructor(opts: PrfSeedStoreOpts);
|
|
43
|
+
get meta(): PrfSeedMeta | undefined;
|
|
44
|
+
setMeta(meta: PrfSeedMeta | undefined): void;
|
|
45
|
+
save(enclave: Enclave, opts?: SetSeedOpts): Promise<Enclave>;
|
|
46
|
+
/**
|
|
47
|
+
* PRF UV inside Enclave, seal master, persist meta (no PRF leaves Enclave).
|
|
48
|
+
*/
|
|
49
|
+
wrapAndSave(enclave: Enclave, opts?: {
|
|
50
|
+
salt?: Uint8Array;
|
|
51
|
+
credId?: Uint8Array;
|
|
52
|
+
createCredIfNeeded?: boolean;
|
|
53
|
+
}): Promise<ValStat<Enclave>>;
|
|
54
|
+
load(): Promise<Enclave | void>;
|
|
55
|
+
/** Passkey UV inside Enclave → new session enclave. */
|
|
56
|
+
unlock(): Promise<ValStat<Enclave>>;
|
|
57
|
+
/**
|
|
58
|
+
* Install sealed meta + session enclave (e.g. after pair-package open) and persist.
|
|
59
|
+
*/
|
|
60
|
+
adoptSealed(enclave: Enclave, meta: PrfSeedMeta): Promise<ValStat<Enclave>>;
|
|
61
|
+
wipe(): Promise<void>;
|
|
62
|
+
}
|
|
@@ -1,111 +1,25 @@
|
|
|
1
1
|
import { Enclave } from "../shared/crypto/enclave";
|
|
2
|
-
import type {
|
|
2
|
+
import type { ICrypto } from "../shared/types";
|
|
3
|
+
import { type ValStat } from "../shared/valstat";
|
|
3
4
|
import type { ISeedStore, SetSeedOpts } from "../types";
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
import { type LargeBlobRp } from "./largeBlob";
|
|
6
|
+
export { defaultWebAuthnRpId } from "./webauthn";
|
|
7
|
+
export { LargeBlob, type LargeBlobCreateOpts, type LargeBlobRp, } from "./largeBlob";
|
|
8
|
+
export type PasskeySeedStoreOpts = LargeBlobRp & {
|
|
9
|
+
crypto: ICrypto;
|
|
10
|
+
credId?: Uint8Array;
|
|
7
11
|
};
|
|
8
12
|
/**
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* Using the full hostname (e.g. life.example.com) scopes credentials to that
|
|
13
|
-
* host only. Safari/Apple Passwords often present the site as the apex domain;
|
|
14
|
-
* Chrome + security keys tend to store whatever rpId we pass. Defaulting to
|
|
15
|
-
* eTLD+1 keeps platform + roaming authenticators on the same RP ID across
|
|
16
|
-
* subdomains and browsers.
|
|
17
|
-
*
|
|
18
|
-
* localhost / IPs are returned unchanged (valid only for those origins).
|
|
19
|
-
*/
|
|
20
|
-
export declare function defaultWebAuthnRpId(hostname: string): string;
|
|
21
|
-
/**
|
|
22
|
-
* Best-effort capability probe (not all UAs expose this accurately).
|
|
23
|
-
*
|
|
24
|
-
* Safari 17+ supports largeBlob on platform authenticators but often omits or
|
|
25
|
-
* mis-reports `extension:largeBlob` from {@link PublicKeyCredential.getClientCapabilities}.
|
|
26
|
-
* Treat Apple Safari as capable so UI can offer enroll; create still checks
|
|
27
|
-
* `largeBlob.supported` after the ceremony.
|
|
28
|
-
*/
|
|
29
|
-
export declare function largeBlobCapable(): Promise<boolean>;
|
|
30
|
-
export type LargeBlobCreateOpts = LargeBlobRp & {
|
|
31
|
-
userName?: string;
|
|
32
|
-
/**
|
|
33
|
-
* Omit (default) to allow both platform (Hello/Touch ID) and roaming
|
|
34
|
-
* authenticators (YubiKey). Set only if you intentionally restrict.
|
|
35
|
-
*/
|
|
36
|
-
authenticatorAttachment?: AuthenticatorAttachment;
|
|
37
|
-
};
|
|
38
|
-
/**
|
|
39
|
-
* Create a discoverable credential with largeBlob support.
|
|
40
|
-
* Does **not** write the seed — call {@link writeLargeBlobSeed} from a
|
|
41
|
-
* **separate user gesture** (second click). Safari / WebKit will not reliably
|
|
42
|
-
* run two WebAuthn ceremonies from one button press; chaining create→write
|
|
43
|
-
* leaves empty passkeys (see blobviem / nsatragno largeBlob demos).
|
|
44
|
-
*
|
|
45
|
-
* On Apple Safari, defaults to `authenticatorAttachment: "platform"` so
|
|
46
|
-
* iCloud Keychain is preferred (Apple largeBlob is platform-oriented).
|
|
47
|
-
* Pass `authenticatorAttachment: "cross-platform"` for security keys.
|
|
48
|
-
*/
|
|
49
|
-
export declare function createLargeBlobCred(opts?: LargeBlobCreateOpts): Promise<Uint8Array>;
|
|
50
|
-
/**
|
|
51
|
-
* Persist seed on an existing largeBlob-capable credential.
|
|
52
|
-
* Must be invoked from its **own** user gesture (not chained after create
|
|
53
|
-
* in the same click handler on Safari).
|
|
54
|
-
*
|
|
55
|
-
* Spec: allowCredentials must contain exactly one credential for write.
|
|
56
|
-
*/
|
|
57
|
-
export declare function writeLargeBlobSeed(credId: Uint8Array, seed: MasterSeed, opts?: LargeBlobRp): Promise<void>;
|
|
58
|
-
/** Overwrite largeBlob with 32 zero bytes (destroys stored seed material). UV required. */
|
|
59
|
-
export declare function clearLargeBlobSeed(credId: Uint8Array, opts?: LargeBlobRp): Promise<void>;
|
|
60
|
-
export type LargeBlobUnlock = {
|
|
61
|
-
seed: MasterSeed;
|
|
62
|
-
/** Authenticator credential id — app should persist for faster local unlock. */
|
|
63
|
-
credId: Uint8Array;
|
|
64
|
-
};
|
|
65
|
-
/**
|
|
66
|
-
* Read seed from largeBlob on a known credential (user verification required).
|
|
67
|
-
*/
|
|
68
|
-
export declare function readLargeBlobSeed(credId: Uint8Array, opts?: LargeBlobRp): Promise<MasterSeed>;
|
|
69
|
-
/**
|
|
70
|
-
* Same as {@link readLargeBlobSeed} but also returns the credential id used.
|
|
71
|
-
*/
|
|
72
|
-
export declare function readLargeBlobUnlock(credId: Uint8Array, opts?: LargeBlobRp): Promise<LargeBlobUnlock>;
|
|
73
|
-
/**
|
|
74
|
-
* Discoverable assertion + largeBlob read (no local credential id).
|
|
75
|
-
*
|
|
76
|
-
* Omits `allowCredentials` so the platform can offer resident keys for this
|
|
77
|
-
* rpId — including iCloud-synced passkeys. Returns seed and `credId` so the
|
|
78
|
-
* app can cache the id for later non-discoverable unlocks.
|
|
79
|
-
*/
|
|
80
|
-
export declare function discoverLargeBlobSeed(opts?: LargeBlobRp): Promise<LargeBlobUnlock>;
|
|
81
|
-
/**
|
|
82
|
-
* Create credential then write seed in one call.
|
|
83
|
-
*
|
|
84
|
-
* **Safari/WebKit:** prefer {@link createLargeBlobCred} + {@link writeLargeBlobSeed}
|
|
85
|
-
* from **two separate clicks**. Chaining both after one gesture often creates a
|
|
86
|
-
* passkey without a blob (`written` false / later `missing blob` on read).
|
|
87
|
-
*
|
|
88
|
-
* Returns credential id — persist it; the seed lives on the authenticator.
|
|
89
|
-
*/
|
|
90
|
-
export declare function storeSeedLargeBlob(seed: MasterSeed, opts?: LargeBlobCreateOpts): Promise<Uint8Array>;
|
|
91
|
-
/**
|
|
92
|
-
* ISeedStore backed by largeBlob.
|
|
93
|
-
* - {@link save} creates (or reuses) cred + writes seed (gestures).
|
|
94
|
-
* - {@link load} returns in-memory enclave only; call {@link unlock} after restart.
|
|
95
|
-
* - Local {@link credId} must be persisted by the app across sessions.
|
|
96
|
-
* - {@link wipe} overwrites largeBlob with zeros (UV), then drops memory + credId.
|
|
97
|
-
* Does not delete the WebAuthn credential from the authenticator.
|
|
13
|
+
* ISeedStore backed by largeBlob. Session secret is always {@link Enclave}.
|
|
14
|
+
* Persist uses {@link Enclave.persistToLargeBlob} / {@link Enclave.fromLargeBlob}.
|
|
98
15
|
*/
|
|
99
16
|
export declare class PasskeySeedStore implements ISeedStore {
|
|
100
17
|
#private;
|
|
101
|
-
constructor(opts
|
|
102
|
-
credId?: Uint8Array;
|
|
103
|
-
});
|
|
18
|
+
constructor(opts: PasskeySeedStoreOpts);
|
|
104
19
|
get credId(): Uint8Array | undefined;
|
|
105
20
|
setCredId(credId: Uint8Array | undefined): void;
|
|
106
|
-
save(
|
|
21
|
+
save(enclave: Enclave, opts?: SetSeedOpts): Promise<Enclave>;
|
|
107
22
|
load(): Promise<Enclave | void>;
|
|
108
|
-
|
|
109
|
-
unlock(): Promise<Enclave>;
|
|
23
|
+
unlock(): Promise<ValStat<Enclave>>;
|
|
110
24
|
wipe(): Promise<void>;
|
|
111
25
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { asPublicKeyCredential, checkWebAuthn, copyToArrayBuffer, defaultWebAuthnRpId, resolveWebAuthnRpId, webAuthnExtensionCapable, COSE_ALG_EDDSA, COSE_ALG_ES256, COSE_ALG_RS256, WEBAUTHN_CHAL_LEN, WEBAUTHN_PUB_KEY_PARAMS, type WebAuthnRp, } from "../shared/webauthn/common";
|
|
@@ -1,46 +1,39 @@
|
|
|
1
1
|
import { IEntDB } from "../entdb/entdb";
|
|
2
2
|
import { IClock } from "../shared/clock";
|
|
3
|
-
import {
|
|
3
|
+
import type { Enclave } from "../shared/crypto/enclave";
|
|
4
|
+
import { IHostConnectionInfo, IStateManager } from "../shared/types";
|
|
4
5
|
import type { IClient, IDiplomaticClientState, IDiplomaticClientXferState, IStore } from "../types";
|
|
5
6
|
export declare function useClientState(client: Pick<IClient<URL>, "clientState">): IDiplomaticClientState | undefined;
|
|
6
7
|
export declare function useClientXferState(client: Pick<IClient<URL>, "xferState">): IDiplomaticClientXferState | undefined;
|
|
7
8
|
export declare function useSyncOnResume(client: Pick<IClient<URL>, "connect" | "sync">): void;
|
|
8
9
|
type UseClientBase = {
|
|
9
10
|
clock?: IClock;
|
|
10
|
-
seed
|
|
11
|
+
/** Session enclave (master seed stays inside Enclave). */
|
|
12
|
+
seed?: Enclave;
|
|
11
13
|
host?: IHostConnectionInfo<URL>;
|
|
12
14
|
readyTimeoutMs?: number;
|
|
13
15
|
};
|
|
14
16
|
/**
|
|
15
|
-
* Worker path:
|
|
16
|
-
*
|
|
17
|
+
* Worker path: IndexedDB on main; sync Worker appears on setSeed via Enclave.
|
|
18
|
+
* Custom `store` is forbidden so durable state cannot diverge.
|
|
17
19
|
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* useClient({ worker: syncWorker, seed, host });
|
|
22
|
-
*
|
|
23
|
-
* Handshake is race-safe (ready event and/or probe ping). See
|
|
24
|
-
* `openDiplomaticClient` for bundler recipes and handshake notes.
|
|
20
|
+
* ```ts
|
|
21
|
+
* useClient({ worker: true, seed, host });
|
|
22
|
+
* ```
|
|
25
23
|
*/
|
|
26
24
|
export type UseClientWorkerOptions = UseClientBase & {
|
|
27
|
-
/**
|
|
28
|
-
|
|
29
|
-
* not each render. Early construction before `useClient` opens IDB is OK —
|
|
30
|
-
* the library does not require catching the unsolicited `ready` event.
|
|
31
|
-
* See `openDiplomaticClient` for instantiation recipes.
|
|
32
|
-
*/
|
|
33
|
-
worker: Worker;
|
|
25
|
+
/** Worker-mode client; Enclave.spawnSyncWorker runs inside setSeed. */
|
|
26
|
+
worker: true;
|
|
34
27
|
store?: never;
|
|
35
28
|
};
|
|
36
29
|
/**
|
|
37
30
|
* Main-thread path. Optional custom store; default IndexedDB.
|
|
38
31
|
*/
|
|
39
32
|
export type UseClientMainOptions = UseClientBase & {
|
|
40
|
-
worker?: undefined;
|
|
33
|
+
worker?: undefined | false;
|
|
41
34
|
/**
|
|
42
35
|
* Protocol store override. Default: IndexedDB. Pass explicitly for
|
|
43
|
-
* MemoryStore or other backends. Incompatible with `worker`.
|
|
36
|
+
* MemoryStore or other backends. Incompatible with `worker: true`.
|
|
44
37
|
*/
|
|
45
38
|
store?: IStore<URL>;
|
|
46
39
|
};
|
|
@@ -3,6 +3,10 @@ export declare function htob(hex: string): Uint8Array;
|
|
|
3
3
|
export declare function bytesEqual(a: Uint8Array, b: Uint8Array): boolean;
|
|
4
4
|
export declare let btob64: (bytes: Uint8Array) => string;
|
|
5
5
|
export declare let b64tob: (b64: string) => Uint8Array;
|
|
6
|
+
/** Bytes → base64url (no padding). For string transport (pair packages, QR payloads). */
|
|
7
|
+
export declare function btob64url(bytes: Uint8Array): string;
|
|
8
|
+
/** Base64url (padding optional) → bytes. */
|
|
9
|
+
export declare function b64urltob(b64url: string): Uint8Array;
|
|
6
10
|
export declare function concat(a: Uint8Array, b: Uint8Array): Uint8Array;
|
|
7
11
|
export declare function btob128(bytes: Uint8Array): string;
|
|
8
12
|
export declare function b128tob(str: string): Uint8Array;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { ICodecStruct } from "../codec.ts";
|
|
2
|
+
import type { IHostConnectionInfo } from "../types.ts";
|
|
3
|
+
/**
|
|
4
|
+
* Wire form of {@link IHostConnectionInfo}: same fields, handle as string URL
|
|
5
|
+
* (live clients use `URL | IProtoHost`).
|
|
6
|
+
*/
|
|
7
|
+
export type BundleHost = Omit<IHostConnectionInfo<URL>, "handle"> & {
|
|
8
|
+
handle: string;
|
|
9
|
+
};
|
|
10
|
+
/** handle (varstring), label (varstring), idx (varint; default 0). */
|
|
11
|
+
export declare const bundleHostCodec: ICodecStruct<BundleHost>;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { ICodecStruct } from "../codec.ts";
|
|
2
|
+
import { type MasterSeed } from "../seed.ts";
|
|
3
|
+
import { type ValStat } from "../valstat.ts";
|
|
4
|
+
import { type BundleHost } from "./bundleHost.ts";
|
|
5
|
+
export { type BundleHost, bundleHostCodec } from "./bundleHost.ts";
|
|
6
|
+
export declare const IDENTITY_BUNDLE_VERSION = 1;
|
|
7
|
+
export type IdentityBundle = {
|
|
8
|
+
v: number;
|
|
9
|
+
masterSeed: MasterSeed;
|
|
10
|
+
hosts: BundleHost[];
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* IdentityBundle wire layout:
|
|
14
|
+
* v: varint
|
|
15
|
+
* masterSeed: 32 fixed bytes
|
|
16
|
+
* hostsLen: varint
|
|
17
|
+
* hosts: hostsLen × BundleHost
|
|
18
|
+
*/
|
|
19
|
+
export declare const identityBundleCodec: ICodecStruct<IdentityBundle>;
|
|
20
|
+
/**
|
|
21
|
+
* Build a wire-format identity bundle from a branded {@link MasterSeed} + hosts.
|
|
22
|
+
* Codec/tests only. Runtime seed I/O must go through Enclave.persistToLargeBlob
|
|
23
|
+
* — never encode then hand seed-bearing bytes to outer layers.
|
|
24
|
+
*/
|
|
25
|
+
export declare function createIdentityBundle(masterSeed: MasterSeed, hosts: BundleHost[]): ValStat<IdentityBundle>;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { ICodecStruct } from "../codec.ts";
|
|
2
|
+
import { type MasterSeed } from "../seed.ts";
|
|
3
|
+
import { type BundleHost } from "./bundleHost.ts";
|
|
4
|
+
export type PairPackagePlain = {
|
|
5
|
+
masterSeed: MasterSeed;
|
|
6
|
+
hosts: BundleHost[];
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Inner plaintext body (not sealed here — AEAD is applied by the pairing
|
|
10
|
+
* ceremony, then carried as `body` in {@link pairPackageEnvelopeCodec}):
|
|
11
|
+
* masterSeed: 32 fixed bytes
|
|
12
|
+
* hostsLen: varint
|
|
13
|
+
* hosts: hostsLen × BundleHost
|
|
14
|
+
*/
|
|
15
|
+
export declare const pairPackagePlainCodec: ICodecStruct<PairPackagePlain>;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { ICodecStruct } from "../codec.ts";
|
|
2
|
+
export declare const PAIR_PACKAGE_VERSION = 1;
|
|
3
|
+
export type PairPackageEnvelope = {
|
|
4
|
+
v: number;
|
|
5
|
+
salt: Uint8Array;
|
|
6
|
+
/** Empty when absent. */
|
|
7
|
+
credId: Uint8Array;
|
|
8
|
+
/** AEAD ciphertext of pairPackagePlainCodec bytes under KDF(PRF). */
|
|
9
|
+
body: Uint8Array;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Outer envelope (metadata is not secret; body is AEAD ciphertext):
|
|
13
|
+
* v: varint
|
|
14
|
+
* salt: varbytes
|
|
15
|
+
* credId: varbytes (length 0 if none)
|
|
16
|
+
* body: varbytes (AEAD of pair-package plain)
|
|
17
|
+
*/
|
|
18
|
+
export declare const pairPackageEnvelopeCodec: ICodecStruct<PairPackageEnvelope>;
|
|
@@ -1,4 +1,26 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { BundleHost } from "../codecs/bundleHost.ts";
|
|
2
|
+
import { Status } from "../consts.ts";
|
|
3
|
+
import { MASTER_SEED_LEN, type SealedMasterKey } from "../seed.ts";
|
|
4
|
+
import type { ICrypto, PublicKey } from "../types.ts";
|
|
5
|
+
import { type ValStat } from "../valstat.ts";
|
|
6
|
+
import { type LargeBlobCreateOpts, type LargeBlobRp } from "../webauthn/largeBlob.ts";
|
|
7
|
+
import { type PrfCreateOpts, type PrfEvalOpts, type PrfRp } from "../webauthn/prf.ts";
|
|
8
|
+
export type { LargeBlobCreateOpts, LargeBlobRp };
|
|
9
|
+
export type { PrfCreateOpts, PrfEvalOpts, PrfRp };
|
|
10
|
+
/** Options for PRF seal/unseal ceremonies (no raw PRF bytes). */
|
|
11
|
+
export type PasskeyPrfOpts = PrfRp & {
|
|
12
|
+
salt?: Uint8Array;
|
|
13
|
+
credId?: Uint8Array;
|
|
14
|
+
/** Seal only: create a PRF credential first when no credId is known. */
|
|
15
|
+
createCredIfNeeded?: boolean;
|
|
16
|
+
userName?: string;
|
|
17
|
+
};
|
|
18
|
+
/** Durable PRF-sealed master + public meta (never includes PRF output). */
|
|
19
|
+
export type PrfSealedMaster = {
|
|
20
|
+
sealedMaster: SealedMasterKey;
|
|
21
|
+
salt: Uint8Array;
|
|
22
|
+
credId: Uint8Array;
|
|
23
|
+
};
|
|
2
24
|
export type EncryptCipher = {
|
|
3
25
|
encrypt: (data: Uint8Array) => Promise<Uint8Array>;
|
|
4
26
|
};
|
|
@@ -25,9 +47,95 @@ export type Identity = {
|
|
|
25
47
|
/** Per-bag KDM mixed with this identity's private key (see bag seal). */
|
|
26
48
|
kdmFor: (msgHeadEnc: Uint8Array) => Promise<Uint8Array>;
|
|
27
49
|
};
|
|
50
|
+
declare const SEAL_KEY_LEN = 32;
|
|
51
|
+
declare const SEAL_PRF_MIN_LEN = 16;
|
|
28
52
|
export declare class Enclave {
|
|
29
53
|
#private;
|
|
30
|
-
constructor(
|
|
54
|
+
private constructor();
|
|
55
|
+
/** New enclave with a fresh 32-byte master seed. */
|
|
56
|
+
static fromRandom(crypto: ICrypto): Promise<Enclave>;
|
|
57
|
+
/**
|
|
58
|
+
* Construct from raw master seed bytes (import / bootstrap only).
|
|
59
|
+
* Copies the seed so callers may zero their buffer after success.
|
|
60
|
+
* Prefer {@link fromRandom} or {@link unsealWithPasskey} for normal flows.
|
|
61
|
+
*/
|
|
62
|
+
static fromBytes(crypto: ICrypto, bytes: Uint8Array): ValStat<Enclave>;
|
|
63
|
+
/**
|
|
64
|
+
* UV + PRF ceremony, then AEAD-seal master. Returns sealed ciphertext + public
|
|
65
|
+
* meta only — never PRF output. Sealed blob on disk is useless without a
|
|
66
|
+
* ceremony Enclave itself runs on unlock.
|
|
67
|
+
*
|
|
68
|
+
* Future: sealWithPassphrase when PRF is unavailable (same sealed layout).
|
|
69
|
+
*/
|
|
70
|
+
sealWithPasskey(opts?: PasskeyPrfOpts): Promise<ValStat<PrfSealedMaster>>;
|
|
71
|
+
/**
|
|
72
|
+
* UV + PRF ceremony, then unseal durable master into a new Enclave.
|
|
73
|
+
* Callers never supply PRF bytes — only sealed meta + RP/salt/credId.
|
|
74
|
+
*/
|
|
75
|
+
static unsealWithPasskey(crypto: ICrypto, sealedMaster: SealedMasterKey, opts: PasskeyPrfOpts & {
|
|
76
|
+
salt: Uint8Array;
|
|
77
|
+
}): Promise<ValStat<Enclave>>;
|
|
78
|
+
/**
|
|
79
|
+
* UV + write IdentityBundle (seed + hosts) to largeBlob.
|
|
80
|
+
* Encode and WebAuthn write complete inside this method; nothing seed-bearing
|
|
81
|
+
* is returned. Prefer empty `hosts` for seed-only backup.
|
|
82
|
+
*
|
|
83
|
+
* Pass `opts.credId` to write an existing credential (one UV). Omit it to
|
|
84
|
+
* create a largeBlob-capable credential then write (two UV). Returns the
|
|
85
|
+
* credential id used.
|
|
86
|
+
*/
|
|
87
|
+
persistToLargeBlob(hosts?: BundleHost[], opts?: LargeBlobCreateOpts & {
|
|
88
|
+
credId?: Uint8Array;
|
|
89
|
+
}): Promise<ValStat<Uint8Array>>;
|
|
90
|
+
/**
|
|
91
|
+
* UV + read largeBlob payload into a new Enclave (+ hosts if IdentityBundle).
|
|
92
|
+
* Seed material never leaves this factory.
|
|
93
|
+
*
|
|
94
|
+
* Pass `opts.credId` for a known credential; omit for discoverable assertion.
|
|
95
|
+
* Returns the credential id used (useful after discover).
|
|
96
|
+
*/
|
|
97
|
+
static fromLargeBlob(crypto: ICrypto, opts?: LargeBlobRp & {
|
|
98
|
+
credId?: Uint8Array;
|
|
99
|
+
}): Promise<ValStat<{
|
|
100
|
+
enclave: Enclave;
|
|
101
|
+
hosts: BundleHost[];
|
|
102
|
+
credId: Uint8Array;
|
|
103
|
+
}>>;
|
|
104
|
+
/** UV + overwrite largeBlob with zeros (destroys stored seed material). */
|
|
105
|
+
static clearLargeBlob(credId: Uint8Array, opts?: LargeBlobRp): Promise<Status>;
|
|
106
|
+
/**
|
|
107
|
+
* UV + PRF, then AEAD-seal pair-package plaintext (seed + hosts).
|
|
108
|
+
* Returns ciphertext body + public ceremony meta (no PRF).
|
|
109
|
+
*/
|
|
110
|
+
sealPairPackageBody(hosts: BundleHost[], opts?: PasskeyPrfOpts): Promise<ValStat<{
|
|
111
|
+
body: Uint8Array;
|
|
112
|
+
salt: Uint8Array;
|
|
113
|
+
credId: Uint8Array;
|
|
114
|
+
}>>;
|
|
115
|
+
/**
|
|
116
|
+
* UV + PRF (using envelope salt/credId), decrypt pair body, build Enclave.
|
|
117
|
+
* Also seals master under the same PRF for durable IDB (single ceremony).
|
|
118
|
+
*/
|
|
119
|
+
static openPairPackageBody(crypto: ICrypto, body: Uint8Array, opts: PasskeyPrfOpts & {
|
|
120
|
+
salt: Uint8Array;
|
|
121
|
+
}): Promise<ValStat<{
|
|
122
|
+
enclave: Enclave;
|
|
123
|
+
hosts: BundleHost[];
|
|
124
|
+
sealedMaster: SealedMasterKey;
|
|
125
|
+
credId: Uint8Array;
|
|
126
|
+
}>>;
|
|
127
|
+
/**
|
|
128
|
+
* Spawn a new sync Worker from the embedded bundle and inject this enclave's
|
|
129
|
+
* master seed into it (transfer). Seed never goes to a caller-supplied port —
|
|
130
|
+
* only to a worker this method just created.
|
|
131
|
+
*
|
|
132
|
+
* Returns the Worker. Caller (WorkerClient) should adopt it and await the
|
|
133
|
+
* `setSeed` reply for `opts.id`.
|
|
134
|
+
*/
|
|
135
|
+
spawnSyncWorker(opts: {
|
|
136
|
+
id: number;
|
|
137
|
+
persist?: boolean;
|
|
138
|
+
}): Worker;
|
|
31
139
|
/**
|
|
32
140
|
* Cipher for public KDM. Does not hold key bytes; each op re-enters the
|
|
33
141
|
* enclave (async, hardware-shaped). `usage` selects the return shape:
|
|
@@ -40,3 +148,6 @@ export declare class Enclave {
|
|
|
40
148
|
*/
|
|
41
149
|
deriveIdentity(keyPath: string, idx?: number): Promise<Identity>;
|
|
42
150
|
}
|
|
151
|
+
/** Derive seal key from PRF output (domain-separated). Used by enclave seal/unseal. */
|
|
152
|
+
export declare function sealKeyFromPrf(crypto: ICrypto, prf: Uint8Array): Promise<ValStat<Uint8Array>>;
|
|
153
|
+
export { MASTER_SEED_LEN, SEAL_KEY_LEN, SEAL_PRF_MIN_LEN };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fill and return `n` cryptographically secure random bytes.
|
|
3
|
+
*/
|
|
4
|
+
export declare function randomBytes(n: number): Uint8Array;
|
|
5
|
+
/**
|
|
6
|
+
* Same as {@link randomBytes}, typed as `Uint8Array<ArrayBuffer>` for DOM
|
|
7
|
+
* `BufferSource` APIs (e.g. WebAuthn challenges and user ids).
|
|
8
|
+
*/
|
|
9
|
+
export declare function randomBytesArrayBuffer(n: number): Uint8Array<ArrayBuffer>;
|
|
10
|
+
/** 32 cryptographically secure random bytes (e.g. master seed material). */
|
|
11
|
+
export declare function random256BitSeed(): Uint8Array;
|