@kubun/credential 0.17.4 → 0.18.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/lib/api.d.ts +61 -30
- package/lib/api.js +455 -111
- package/lib/authority.d.ts +4 -0
- package/lib/authority.js +3 -0
- package/lib/branch.d.ts +4 -0
- package/lib/branch.js +24 -0
- package/lib/builder.d.ts +5 -0
- package/lib/builder.js +8 -6
- package/lib/canonical-token.d.ts +2 -0
- package/lib/canonical-token.js +10 -0
- package/lib/errors.d.ts +39 -1
- package/lib/errors.js +77 -0
- package/lib/fork-repair.d.ts +19 -0
- package/lib/fork-repair.js +3 -0
- package/lib/grantor-authority.d.ts +26 -0
- package/lib/grantor-authority.js +43 -0
- package/lib/index.d.ts +15 -6
- package/lib/index.js +7 -2
- package/lib/key-cache.d.ts +4 -3
- package/lib/key-cache.js +22 -15
- package/lib/manager-context.d.ts +50 -0
- package/lib/manager-context.js +82 -0
- package/lib/manager-entries.d.ts +3 -0
- package/lib/manager-entries.js +585 -0
- package/lib/manager-keys.d.ts +3 -0
- package/lib/manager-keys.js +413 -0
- package/lib/manager-open.d.ts +45 -0
- package/lib/manager-open.js +421 -0
- package/lib/manager-records.d.ts +94 -0
- package/lib/manager-records.js +291 -0
- package/lib/manager-removal-recovery.d.ts +7 -0
- package/lib/manager-removal-recovery.js +211 -0
- package/lib/manager-rotate.d.ts +15 -0
- package/lib/manager-rotate.js +536 -0
- package/lib/manager-types.d.ts +337 -0
- package/lib/manager-types.js +1 -0
- package/lib/manager.d.ts +2 -293
- package/lib/manager.js +38 -1073
- package/lib/manifest-token.d.ts +31 -0
- package/lib/manifest-token.js +51 -0
- package/lib/provider.d.ts +15 -2
- package/lib/provider.js +39 -0
- package/lib/record-types.d.ts +65 -0
- package/lib/record-types.js +865 -0
- package/lib/refreshing-credential-provider.d.ts +6 -2
- package/lib/refreshing-credential-provider.js +76 -13
- package/lib/suite.d.ts +5 -4
- package/lib/suite.js +2 -2
- package/package.json +16 -15
- package/lib/op.d.ts +0 -128
- package/lib/op.js +0 -304
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
import type { MethodRegistry, SigningIdentity } from '@kokuin/token';
|
|
2
|
+
import type { HLC } from '@kubun/hlc';
|
|
3
|
+
import type { CredentialStoreAPI } from '@kubun/store-credential';
|
|
4
|
+
import type { Runtime } from '@sozai/runtime';
|
|
5
|
+
import type { CredentialAuthority } from './authority.js';
|
|
6
|
+
import type { ForkRepairResult } from './fork-repair.js';
|
|
7
|
+
import type { PinThrottleParams } from './pin-throttle.js';
|
|
8
|
+
import type { CredentialEntryProvenance } from './provenance.js';
|
|
9
|
+
import type { FactorInput, FactorInputs, SecretFactorKind } from './suite.js';
|
|
10
|
+
export type CreateCredentialManagerParams = {
|
|
11
|
+
store: CredentialStoreAPI;
|
|
12
|
+
/**
|
|
13
|
+
* This device's identity. NOT required to be a `KeyAgreementIdentity`: a device
|
|
14
|
+
* that cannot decrypt can still read metadata, and will be able to unwrap by
|
|
15
|
+
* passphrase once that factor exists. What it cannot do is take the `did`
|
|
16
|
+
* default in {@link CredentialManager.createKey}.
|
|
17
|
+
*
|
|
18
|
+
* It MUST be able to sign, which a reading identity need not: every row this
|
|
19
|
+
* manager writes carries a signed op, and a device that could write rows no
|
|
20
|
+
* peer can authenticate would be writing into a lane that has to drop them.
|
|
21
|
+
*/
|
|
22
|
+
identity: SigningIdentity;
|
|
23
|
+
/**
|
|
24
|
+
* Host runtime, propagated rather than constructed here. This package runs on
|
|
25
|
+
* React Native and in browsers as well as on node, and content keys, IVs and
|
|
26
|
+
* factor secrets are the values that most need the platform's own CSPRNG
|
|
27
|
+
* rather than whatever `crypto` a bundler resolved.
|
|
28
|
+
*/
|
|
29
|
+
runtime: Runtime;
|
|
30
|
+
/**
|
|
31
|
+
* The device's one clock, passed in rather than built here.
|
|
32
|
+
*
|
|
33
|
+
* Required, and deliberately not defaulted: a connector builds one manager per
|
|
34
|
+
* transaction, so a manager that constructed its own would start from
|
|
35
|
+
* `lastWallTime = 0` every time and two of them writing in the same
|
|
36
|
+
* millisecond would emit an identical `(wallTime, counter, nodeID)` — a
|
|
37
|
+
* colliding merkle leaf and an order tie with no tiebreak.
|
|
38
|
+
*/
|
|
39
|
+
hlc: HLC;
|
|
40
|
+
/**
|
|
41
|
+
* Who this identity is relative to a key. Defaults to the device answer:
|
|
42
|
+
* it administers what it owns and nothing else.
|
|
43
|
+
*
|
|
44
|
+
* The same port the plugin's query context takes, deliberately — a device
|
|
45
|
+
* authority refuses a grant recipient gating someone else's key, and a
|
|
46
|
+
* delegation authority will let an agent gate its controller's, without
|
|
47
|
+
* either layer growing a predicate of its own.
|
|
48
|
+
*/
|
|
49
|
+
authority?: CredentialAuthority;
|
|
50
|
+
/** Controller-owned writes require a recovery check; an absent check fails closed. */
|
|
51
|
+
removalRecovery?: (RecoverRemovedDevicesParams & {
|
|
52
|
+
headVersion(ownerDID: string): Promise<string>;
|
|
53
|
+
}) | {
|
|
54
|
+
ensure(): Promise<void>;
|
|
55
|
+
removedDevices?(ownerDID: string): Promise<ReadonlySet<string>>;
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* How long an unlocked key stays in memory. Eviction is lazy — checked when
|
|
59
|
+
* the cache is read — because a timer would keep the host process alive and
|
|
60
|
+
* make every test wait on a clock it does not control.
|
|
61
|
+
*/
|
|
62
|
+
unlockTTL?: number;
|
|
63
|
+
/**
|
|
64
|
+
* Failed-PIN backoff. Its clock comes from `now`, so a caller cannot give the
|
|
65
|
+
* throttle a different notion of time from the cache's.
|
|
66
|
+
*/
|
|
67
|
+
pinThrottle?: Omit<PinThrottleParams, 'now'>;
|
|
68
|
+
/** Injectable so a TTL or backoff test does not sleep. */
|
|
69
|
+
now?: () => number;
|
|
70
|
+
};
|
|
71
|
+
/** The device that may author a live OAuth grant and the grant's local name. */
|
|
72
|
+
export type GrantEntryParams = {
|
|
73
|
+
grantDeviceDID: string;
|
|
74
|
+
grantName: string;
|
|
75
|
+
grantOwnerDID?: string;
|
|
76
|
+
};
|
|
77
|
+
export type CreateKeyParams = {
|
|
78
|
+
suite?: number;
|
|
79
|
+
/** One wrapping, whose factors are an AND. Shorthand for a single-entry `wrappings`. */
|
|
80
|
+
factors?: Array<FactorInput>;
|
|
81
|
+
/**
|
|
82
|
+
* Several wrappings — an OR of ANDs, one per way of opening the key.
|
|
83
|
+
*
|
|
84
|
+
* A key addressed to two recipients is not two keys and is not a grant: both
|
|
85
|
+
* wrappings exist from the first write, which is what a connector needs when
|
|
86
|
+
* the account owner and the server that runs sync are different DIDs.
|
|
87
|
+
*/
|
|
88
|
+
wrappings?: Array<Array<FactorInput>>;
|
|
89
|
+
/**
|
|
90
|
+
* Who may later grant, rotate and revoke. Defaults to this device, which is
|
|
91
|
+
* the peer case. A server creating a key on a viewer's behalf passes the
|
|
92
|
+
* viewer, and stays a recipient rather than the owner.
|
|
93
|
+
*/
|
|
94
|
+
ownerDID?: string;
|
|
95
|
+
/**
|
|
96
|
+
* Resolvers for recipient DIDs that carry no key material of their own
|
|
97
|
+
* (`did:kokuin:`). Passed per call rather than held on the manager, so a
|
|
98
|
+
* request-scoped resolver never outlives its request in the cached manager.
|
|
99
|
+
*/
|
|
100
|
+
methods?: MethodRegistry;
|
|
101
|
+
};
|
|
102
|
+
export type RotateKeyParams = {
|
|
103
|
+
/**
|
|
104
|
+
* Migrate to a different ciphersuite. Rotation is the only path between
|
|
105
|
+
* suites: it already re-encrypts every entry and re-makes every wrapping,
|
|
106
|
+
* which is exactly what a suite change needs.
|
|
107
|
+
*/
|
|
108
|
+
suite?: number;
|
|
109
|
+
/**
|
|
110
|
+
* Secrets for the surviving factors, keyed by kind — the same prompt
|
|
111
|
+
* `addFactor` needs, for the same reason: a descriptor holds a salt and a
|
|
112
|
+
* cost, never the secret, so a wrapping carrying one cannot be re-made at the
|
|
113
|
+
* new version without it.
|
|
114
|
+
*
|
|
115
|
+
* Only the rotating identity's own wrappings can carry a secret factor, since
|
|
116
|
+
* `addFactor` refuses one on a key it does not administer — so the prompt is
|
|
117
|
+
* always one the caller is present for.
|
|
118
|
+
*/
|
|
119
|
+
inputs?: FactorInputs;
|
|
120
|
+
/** Resolvers for recipient DIDs that need one (`did:kokuin:`), for re-made `did` wrappings. */
|
|
121
|
+
methods?: MethodRegistry;
|
|
122
|
+
/**
|
|
123
|
+
* Overrides the constructed authority for this one call, so a provider-cached
|
|
124
|
+
* manager holds no request state. No production caller feeds one yet — the
|
|
125
|
+
* group-grant wiring that will is a follow-up.
|
|
126
|
+
*/
|
|
127
|
+
authority?: CredentialAuthority;
|
|
128
|
+
};
|
|
129
|
+
/** Optionals shared by the factor-editing verbs `addFactor` and `removeFactor`. */
|
|
130
|
+
export type FactorChangeParams = {
|
|
131
|
+
/**
|
|
132
|
+
* Secrets for the factors that SURVIVE the change, keyed by kind — a
|
|
133
|
+
* descriptor holds a salt, never a secret, so the surviving wrapping's KEK
|
|
134
|
+
* cannot be re-derived without them.
|
|
135
|
+
*/
|
|
136
|
+
inputs?: FactorInputs;
|
|
137
|
+
/** Resolvers for recipient DIDs that need one (`did:kokuin:`), for re-made `did` wrappings. */
|
|
138
|
+
methods?: MethodRegistry;
|
|
139
|
+
/** Per-call authority overriding the constructed default. See `RotateKeyParams.authority`. */
|
|
140
|
+
authority?: CredentialAuthority;
|
|
141
|
+
};
|
|
142
|
+
/** See `grant`. */
|
|
143
|
+
export type GrantKeyParams = {
|
|
144
|
+
/** Resolvers for recipient DIDs that carry no key material of their own (`did:kokuin:`). */
|
|
145
|
+
methods?: MethodRegistry;
|
|
146
|
+
/** Per-call authority overriding the constructed default. See `RotateKeyParams.authority`. */
|
|
147
|
+
authority?: CredentialAuthority;
|
|
148
|
+
};
|
|
149
|
+
/** See `reKeyEntry`. */
|
|
150
|
+
export type RecoverRemovedDevicesParams = {
|
|
151
|
+
/** Current-head membership resolver; a removed device resolves to null. */
|
|
152
|
+
resolveController(did: string): Promise<string | null>;
|
|
153
|
+
/** Current controller denial set, including recipients that authored no record. */
|
|
154
|
+
removedDevices?(ownerDID: string): Promise<ReadonlySet<string>>;
|
|
155
|
+
methods?: MethodRegistry;
|
|
156
|
+
};
|
|
157
|
+
export type ReKeyEntryParams = {
|
|
158
|
+
/** Per-call authority overriding the constructed default. See `RotateKeyParams.authority`. */
|
|
159
|
+
authority?: CredentialAuthority;
|
|
160
|
+
};
|
|
161
|
+
export type CredentialEntryAddress = {
|
|
162
|
+
entryID: string;
|
|
163
|
+
ownerDID: string;
|
|
164
|
+
};
|
|
165
|
+
export type UpdateCredentialEntryIfUnchangedParams = CredentialEntryAddress & {
|
|
166
|
+
plaintext: Uint8Array;
|
|
167
|
+
expectedHLC: string;
|
|
168
|
+
};
|
|
169
|
+
export type ReKeyCredentialEntryParams = CredentialEntryAddress & ReKeyEntryParams & {
|
|
170
|
+
toKeyID: string;
|
|
171
|
+
};
|
|
172
|
+
/**
|
|
173
|
+
* Why a key cannot be opened here, as opposed to not being open yet.
|
|
174
|
+
*
|
|
175
|
+
* Carried so that `status` can answer rather than throw: the one call whose job
|
|
176
|
+
* is to report a key as unreadable must not fail on the key that is unreadable.
|
|
177
|
+
*/
|
|
178
|
+
export type CredentialUnopenableReason =
|
|
179
|
+
/** A newer peer's ciphersuite. A different build could open it; this one cannot. */
|
|
180
|
+
'unsupported-suite'
|
|
181
|
+
/** No wrapping at the key's current version — a rotation that did not finish. */
|
|
182
|
+
| 'no-wrapping'
|
|
183
|
+
/** Every wrapping belongs to another recipient. Another device can open it. */
|
|
184
|
+
| 'not-addressed' | 'revoked';
|
|
185
|
+
/**
|
|
186
|
+
* Derived, never stored. A persisted `unlocked` column would be a belief that
|
|
187
|
+
* survives a restart it should not.
|
|
188
|
+
*
|
|
189
|
+
* `available` is separated from `unlocked` because they differ in what the
|
|
190
|
+
* caller must do, which is nothing, and in what the machine must do, which is
|
|
191
|
+
* one unwrap. A `did`-only key is `available` until first read, and a TTL
|
|
192
|
+
* eviction returns it there rather than to `locked` — no prompt ever becomes
|
|
193
|
+
* necessary.
|
|
194
|
+
*/
|
|
195
|
+
export type CredentialKeyStatus = {
|
|
196
|
+
state: 'unlocked';
|
|
197
|
+
} | {
|
|
198
|
+
state: 'available';
|
|
199
|
+
} | {
|
|
200
|
+
state: 'locked';
|
|
201
|
+
/**
|
|
202
|
+
* One entry per wrapping that could be opened here: an OR of ANDs. Nested
|
|
203
|
+
* because flattening loses the difference between a key openable by a pin
|
|
204
|
+
* OR a passphrase and one demanding both, and those are different prompts.
|
|
205
|
+
* `did` factors are absent — this device supplies them without asking.
|
|
206
|
+
*/
|
|
207
|
+
unlockOptions: Array<Array<SecretFactorKind>>;
|
|
208
|
+
} | {
|
|
209
|
+
state: 'unopenable';
|
|
210
|
+
reason: CredentialUnopenableReason;
|
|
211
|
+
};
|
|
212
|
+
export type CredentialGrantResult = {
|
|
213
|
+
/** The key version the recipient's wrapping sits at. */
|
|
214
|
+
version: number;
|
|
215
|
+
/**
|
|
216
|
+
* False when the recipient already held a wrapping and nothing was written.
|
|
217
|
+
* A caller broadcasting the grant emits its frame either way — that is what
|
|
218
|
+
* makes a repeat grant the retry path for a frame that never arrived.
|
|
219
|
+
*/
|
|
220
|
+
created: boolean;
|
|
221
|
+
};
|
|
222
|
+
export type CredentialManager = {
|
|
223
|
+
/** Store instance this manager reads and writes through. */
|
|
224
|
+
readonly store: CredentialStoreAPI;
|
|
225
|
+
/** Factor kinds this device can supply. Empty means it can hold no credentials. */
|
|
226
|
+
availableFactors(): Array<string>;
|
|
227
|
+
/**
|
|
228
|
+
* Open a key and hold its content key in memory.
|
|
229
|
+
*
|
|
230
|
+
* A `did`-only key needs no call: it opens on demand. This is for the ones
|
|
231
|
+
* carrying a factor only the user can supply.
|
|
232
|
+
*/
|
|
233
|
+
unlock(keyID: string, inputs?: FactorInputs): Promise<void>;
|
|
234
|
+
/** Zero the cached content key. TTL eviction does the same thing. */
|
|
235
|
+
lock(keyID: string): void;
|
|
236
|
+
lockAll(): void;
|
|
237
|
+
status(keyID: string, version?: number): Promise<CredentialKeyStatus>;
|
|
238
|
+
/**
|
|
239
|
+
* Add a factor to THIS device's wrapping, leaving every other recipient's
|
|
240
|
+
* alone. Adding a `pin` therefore gates this device and no other, which is the
|
|
241
|
+
* intended granularity.
|
|
242
|
+
*
|
|
243
|
+
* `params.inputs` must carry the secret for every factor that SURVIVES the
|
|
244
|
+
* change, not only the one being added: the new wrapping's key is derived from
|
|
245
|
+
* all of them, and a descriptor holds a salt, never a secret.
|
|
246
|
+
*/
|
|
247
|
+
addFactor(keyID: string, factor: FactorInput, params?: FactorChangeParams): Promise<void>;
|
|
248
|
+
/** Remove a factor kind from this device's wrapping. See `addFactor` on `params.inputs`. */
|
|
249
|
+
removeFactor(keyID: string, kind: string, params?: FactorChangeParams): Promise<void>;
|
|
250
|
+
createKey(params?: CreateKeyParams): Promise<string>;
|
|
251
|
+
/**
|
|
252
|
+
* Retire this key's content key: mint a fresh one at `version + 1`,
|
|
253
|
+
* re-encrypt every entry under it and re-make every wrapping. Returns the new
|
|
254
|
+
* version, which a caller has to carry in the `credential:key-rotate` frame.
|
|
255
|
+
*
|
|
256
|
+
* `keyID` and every `entryID` survive, so documents referencing them do not
|
|
257
|
+
* move. A wrapping survives only if this device can re-make it: `did`-only
|
|
258
|
+
* ones re-make unattended, and one carrying a secret factor needs that secret
|
|
259
|
+
* back in `params.inputs`, because re-making a wrapping derives its KEK from
|
|
260
|
+
* every surviving factor and a descriptor holds a salt, never a secret.
|
|
261
|
+
*
|
|
262
|
+
* The key, wrappings and entries commit in one store transaction.
|
|
263
|
+
*/
|
|
264
|
+
rotate(keyID: string, params?: RotateKeyParams): Promise<number>;
|
|
265
|
+
repairFork(keyID: string, inputs?: FactorInputs): Promise<ForkRepairResult>;
|
|
266
|
+
/** Reconcile records authored by former members of this device's controller. */
|
|
267
|
+
recoverRemovedDevices(params: RecoverRemovedDevicesParams): Promise<number>;
|
|
268
|
+
ensureRemovalRecovery(): Promise<void>;
|
|
269
|
+
revoke(keyID: string, recipientDID: string, params?: RotateKeyParams): Promise<number>;
|
|
270
|
+
/**
|
|
271
|
+
* Address this key to one more recipient: a `did` wrapping over the SAME
|
|
272
|
+
* content key at the key's current version. Every entry already written stays
|
|
273
|
+
* as it is — the recipient opens the key, not a copy of it — and no entry
|
|
274
|
+
* plaintext is touched. Returns the version the wrapping was made at.
|
|
275
|
+
*
|
|
276
|
+
* A recipient that already holds a wrapping is a no-op, so the version alone
|
|
277
|
+
* cannot tell a fresh grant from a re-grant — `created` is what distinguishes
|
|
278
|
+
* them.
|
|
279
|
+
*
|
|
280
|
+
* `recipientWrappableDID` must be RESOLVABLE, not merely a name: a
|
|
281
|
+
* `did:peer:4` short form carries no document and so no agreement key. Working
|
|
282
|
+
* out which string that is for a given party is a group question, and this
|
|
283
|
+
* package does not know what a group is — so it takes the answer rather than
|
|
284
|
+
* asking.
|
|
285
|
+
*
|
|
286
|
+
* `administer`, and the device must be able to open the key: a locked one
|
|
287
|
+
* fails before the first write rather than persisting half a grant.
|
|
288
|
+
*/
|
|
289
|
+
grant(keyID: string, recipientWrappableDID: string, params?: GrantKeyParams): Promise<CredentialGrantResult>;
|
|
290
|
+
listKeys(): Promise<Array<string>>;
|
|
291
|
+
/** How many wrappings a key carries — one per way of opening it. */
|
|
292
|
+
countWrappings(keyID: string): Promise<number>;
|
|
293
|
+
deleteKey(keyID: string): Promise<void>;
|
|
294
|
+
putEntry(keyID: string, plaintext: Uint8Array, grant?: GrantEntryParams): Promise<string>;
|
|
295
|
+
updateEntry(address: CredentialEntryAddress, plaintext: Uint8Array): Promise<void>;
|
|
296
|
+
/**
|
|
297
|
+
* As {@link updateEntry}, but never inserts: an entry gone by the time the
|
|
298
|
+
* write lands reports `'absent'` instead of raising. For a writeback racing a
|
|
299
|
+
* concurrent delete, where the caller has no `keyID` to fall back to `putEntry`
|
|
300
|
+
* with and raising would just be a `catch` the caller re-swallows.
|
|
301
|
+
*/
|
|
302
|
+
updateEntryIfPresent(address: CredentialEntryAddress, plaintext: Uint8Array): Promise<'updated' | 'absent'>;
|
|
303
|
+
/**
|
|
304
|
+
* As {@link updateEntryIfPresent}, and also only while the entry still carries
|
|
305
|
+
* `expectedHLC`, the stamp its caller read it at. An entry written since reports
|
|
306
|
+
* `'superseded'` and is left as it is.
|
|
307
|
+
*/
|
|
308
|
+
updateEntryIfUnchanged(params: UpdateCredentialEntryIfUnchangedParams): Promise<'updated' | 'absent' | 'superseded'>;
|
|
309
|
+
readEntry(address: CredentialEntryAddress): Promise<Uint8Array>;
|
|
310
|
+
getEntryProvenance(address: CredentialEntryAddress): Promise<CredentialEntryProvenance>;
|
|
311
|
+
/**
|
|
312
|
+
* The RAW `owner_did` off the entry's key row — never normalized. Callers
|
|
313
|
+
* comparing against another DID normalize it themselves; folding it here
|
|
314
|
+
* would make the answer wrong for whichever caller wanted the unfolded form.
|
|
315
|
+
* `null` when the entry, or the key it sits on, is absent.
|
|
316
|
+
*/
|
|
317
|
+
getEntryKeyOwnerDID(address: CredentialEntryAddress): Promise<string | null>;
|
|
318
|
+
listEntries(keyID: string): Promise<Array<string>>;
|
|
319
|
+
deleteEntry(address: CredentialEntryAddress): Promise<void>;
|
|
320
|
+
/** Refresh this device's older OAuth grant entries after a received key rotation. */
|
|
321
|
+
reencryptGrantEntries(keyID: string): Promise<void>;
|
|
322
|
+
/**
|
|
323
|
+
* Move an entry onto another key: decrypt under the one it sits on, re-encrypt
|
|
324
|
+
* under the target's current version, keep the entry id.
|
|
325
|
+
*
|
|
326
|
+
* The key is the sharing unit, so sharing one secret means moving it onto a
|
|
327
|
+
* shared key rather than sharing the key it already sits under, which would
|
|
328
|
+
* share every other entry beneath that one too. The entry id survives because
|
|
329
|
+
* it never embedded a key id — every document referencing it stays valid. The
|
|
330
|
+
* ciphertext does not survive: it is bound to its key, so this is a genuine
|
|
331
|
+
* re-encryption and the target must be open, not merely named.
|
|
332
|
+
*
|
|
333
|
+
* `administer` on BOTH keys. Writing under the target changes what the target
|
|
334
|
+
* holds, and taking an entry out of the source changes what the source holds.
|
|
335
|
+
*/
|
|
336
|
+
reKeyEntry(params: ReKeyCredentialEntryParams): Promise<void>;
|
|
337
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|
package/lib/manager.d.ts
CHANGED
|
@@ -1,294 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
import type { CredentialStoreAPI } from '@kubun/store-credential';
|
|
4
|
-
import type { Runtime } from '@sozai/runtime';
|
|
5
|
-
import { type CredentialAuthority } from './authority.js';
|
|
6
|
-
import { type PinThrottleParams } from './pin-throttle.js';
|
|
7
|
-
import { type CredentialEntryProvenance } from './provenance.js';
|
|
8
|
-
import { type FactorInput, type FactorInputs, type SecretFactorKind } from './suite.js';
|
|
9
|
-
export type CreateCredentialManagerParams = {
|
|
10
|
-
store: CredentialStoreAPI;
|
|
11
|
-
/**
|
|
12
|
-
* This device's identity. NOT required to be a `KeyAgreementIdentity`: a device
|
|
13
|
-
* that cannot decrypt can still read metadata, and will be able to unwrap by
|
|
14
|
-
* passphrase once that factor exists. What it cannot do is take the `did`
|
|
15
|
-
* default in {@link CredentialManager.createKey}.
|
|
16
|
-
*
|
|
17
|
-
* It MUST be able to sign, which a reading identity need not: every row this
|
|
18
|
-
* manager writes carries a signed op, and a device that could write rows no
|
|
19
|
-
* peer can authenticate would be writing into a lane that has to drop them.
|
|
20
|
-
*/
|
|
21
|
-
identity: SigningIdentity;
|
|
22
|
-
/**
|
|
23
|
-
* Host runtime, propagated rather than constructed here. This package runs on
|
|
24
|
-
* React Native and in browsers as well as on node, and content keys, IVs and
|
|
25
|
-
* factor secrets are the values that most need the platform's own CSPRNG
|
|
26
|
-
* rather than whatever `crypto` a bundler resolved.
|
|
27
|
-
*/
|
|
28
|
-
runtime: Runtime;
|
|
29
|
-
/**
|
|
30
|
-
* The device's one clock, passed in rather than built here.
|
|
31
|
-
*
|
|
32
|
-
* Required, and deliberately not defaulted: a connector builds one manager per
|
|
33
|
-
* transaction, so a manager that constructed its own would start from
|
|
34
|
-
* `lastWallTime = 0` every time and two of them writing in the same
|
|
35
|
-
* millisecond would emit an identical `(wallTime, counter, nodeID)` — a
|
|
36
|
-
* colliding merkle leaf and an order tie with no tiebreak.
|
|
37
|
-
*/
|
|
38
|
-
hlc: HLC;
|
|
39
|
-
/**
|
|
40
|
-
* Who this identity is relative to a key. Defaults to the device answer:
|
|
41
|
-
* it administers what it owns and nothing else.
|
|
42
|
-
*
|
|
43
|
-
* The same port the plugin's query context takes, deliberately — a device
|
|
44
|
-
* authority refuses a grant recipient gating someone else's key, and a
|
|
45
|
-
* delegation authority will let an agent gate its controller's, without
|
|
46
|
-
* either layer growing a predicate of its own.
|
|
47
|
-
*/
|
|
48
|
-
authority?: CredentialAuthority;
|
|
49
|
-
/**
|
|
50
|
-
* How long an unlocked key stays in memory. Eviction is lazy — checked when
|
|
51
|
-
* the cache is read — because a timer would keep the host process alive and
|
|
52
|
-
* make every test wait on a clock it does not control.
|
|
53
|
-
*/
|
|
54
|
-
unlockTTL?: number;
|
|
55
|
-
/**
|
|
56
|
-
* Failed-PIN backoff. Its clock comes from `now`, so a caller cannot give the
|
|
57
|
-
* throttle a different notion of time from the cache's.
|
|
58
|
-
*/
|
|
59
|
-
pinThrottle?: Omit<PinThrottleParams, 'now'>;
|
|
60
|
-
/** Injectable so a TTL or backoff test does not sleep. */
|
|
61
|
-
now?: () => number;
|
|
62
|
-
};
|
|
63
|
-
export type CreateKeyParams = {
|
|
64
|
-
suite?: number;
|
|
65
|
-
/** One wrapping, whose factors are an AND. Shorthand for a single-entry `wrappings`. */
|
|
66
|
-
factors?: Array<FactorInput>;
|
|
67
|
-
/**
|
|
68
|
-
* Several wrappings — an OR of ANDs, one per way of opening the key.
|
|
69
|
-
*
|
|
70
|
-
* A key addressed to two recipients is not two keys and is not a grant: both
|
|
71
|
-
* wrappings exist from the first write, which is what a connector needs when
|
|
72
|
-
* the account owner and the server that runs sync are different DIDs.
|
|
73
|
-
*/
|
|
74
|
-
wrappings?: Array<Array<FactorInput>>;
|
|
75
|
-
/**
|
|
76
|
-
* Who may later grant, rotate and revoke. Defaults to this device, which is
|
|
77
|
-
* the peer case. A server creating a key on a viewer's behalf passes the
|
|
78
|
-
* viewer, and stays a recipient rather than the owner.
|
|
79
|
-
*/
|
|
80
|
-
ownerDID?: string;
|
|
81
|
-
/**
|
|
82
|
-
* Resolvers for recipient DIDs that carry no key material of their own
|
|
83
|
-
* (`did:kokuin:`). Passed per call rather than held on the manager, so a
|
|
84
|
-
* request-scoped resolver never outlives its request in the cached manager.
|
|
85
|
-
*/
|
|
86
|
-
methods?: MethodRegistry;
|
|
87
|
-
};
|
|
88
|
-
export type RotateKeyParams = {
|
|
89
|
-
/**
|
|
90
|
-
* Migrate to a different ciphersuite. Rotation is the only path between
|
|
91
|
-
* suites: it already re-encrypts every entry and re-makes every wrapping,
|
|
92
|
-
* which is exactly what a suite change needs.
|
|
93
|
-
*/
|
|
94
|
-
suite?: number;
|
|
95
|
-
/**
|
|
96
|
-
* Secrets for the surviving factors, keyed by kind — the same prompt
|
|
97
|
-
* `addFactor` needs, for the same reason: a descriptor holds a salt and a
|
|
98
|
-
* cost, never the secret, so a wrapping carrying one cannot be re-made at the
|
|
99
|
-
* new version without it.
|
|
100
|
-
*
|
|
101
|
-
* Only the rotating identity's own wrappings can carry a secret factor, since
|
|
102
|
-
* `addFactor` refuses one on a key it does not administer — so the prompt is
|
|
103
|
-
* always one the caller is present for.
|
|
104
|
-
*/
|
|
105
|
-
inputs?: FactorInputs;
|
|
106
|
-
/** Resolvers for recipient DIDs that need one (`did:kokuin:`), for re-made `did` wrappings. */
|
|
107
|
-
methods?: MethodRegistry;
|
|
108
|
-
/**
|
|
109
|
-
* Overrides the constructed authority for this one call, so a provider-cached
|
|
110
|
-
* manager holds no request state. No production caller feeds one yet — the
|
|
111
|
-
* group-grant wiring that will is a follow-up.
|
|
112
|
-
*/
|
|
113
|
-
authority?: CredentialAuthority;
|
|
114
|
-
};
|
|
115
|
-
/** Optionals shared by the factor-editing verbs `addFactor` and `removeFactor`. */
|
|
116
|
-
export type FactorChangeParams = {
|
|
117
|
-
/**
|
|
118
|
-
* Secrets for the factors that SURVIVE the change, keyed by kind — a
|
|
119
|
-
* descriptor holds a salt, never a secret, so the surviving wrapping's KEK
|
|
120
|
-
* cannot be re-derived without them.
|
|
121
|
-
*/
|
|
122
|
-
inputs?: FactorInputs;
|
|
123
|
-
/** Resolvers for recipient DIDs that need one (`did:kokuin:`), for re-made `did` wrappings. */
|
|
124
|
-
methods?: MethodRegistry;
|
|
125
|
-
/** Per-call authority overriding the constructed default. See `RotateKeyParams.authority`. */
|
|
126
|
-
authority?: CredentialAuthority;
|
|
127
|
-
};
|
|
128
|
-
/** See `grant`. */
|
|
129
|
-
export type GrantKeyParams = {
|
|
130
|
-
/** Resolvers for recipient DIDs that carry no key material of their own (`did:kokuin:`). */
|
|
131
|
-
methods?: MethodRegistry;
|
|
132
|
-
/** Per-call authority overriding the constructed default. See `RotateKeyParams.authority`. */
|
|
133
|
-
authority?: CredentialAuthority;
|
|
134
|
-
};
|
|
135
|
-
/** See `reKeyEntry`. */
|
|
136
|
-
export type ReKeyEntryParams = {
|
|
137
|
-
/** Per-call authority overriding the constructed default. See `RotateKeyParams.authority`. */
|
|
138
|
-
authority?: CredentialAuthority;
|
|
139
|
-
};
|
|
140
|
-
/**
|
|
141
|
-
* Why a key cannot be opened here, as opposed to not being open yet.
|
|
142
|
-
*
|
|
143
|
-
* Carried so that `status` can answer rather than throw: the one call whose job
|
|
144
|
-
* is to report a key as unreadable must not fail on the key that is unreadable.
|
|
145
|
-
*/
|
|
146
|
-
export type CredentialUnopenableReason =
|
|
147
|
-
/** A newer peer's ciphersuite. A different build could open it; this one cannot. */
|
|
148
|
-
'unsupported-suite'
|
|
149
|
-
/** No wrapping at the key's current version — a rotation that did not finish. */
|
|
150
|
-
| 'no-wrapping'
|
|
151
|
-
/** Every wrapping belongs to another recipient. Another device can open it. */
|
|
152
|
-
| 'not-addressed';
|
|
153
|
-
/**
|
|
154
|
-
* Derived, never stored. A persisted `unlocked` column would be a belief that
|
|
155
|
-
* survives a restart it should not.
|
|
156
|
-
*
|
|
157
|
-
* `available` is separated from `unlocked` because they differ in what the
|
|
158
|
-
* caller must do, which is nothing, and in what the machine must do, which is
|
|
159
|
-
* one unwrap. A `did`-only key is `available` until first read, and a TTL
|
|
160
|
-
* eviction returns it there rather than to `locked` — no prompt ever becomes
|
|
161
|
-
* necessary.
|
|
162
|
-
*/
|
|
163
|
-
export type CredentialKeyStatus = {
|
|
164
|
-
state: 'unlocked';
|
|
165
|
-
} | {
|
|
166
|
-
state: 'available';
|
|
167
|
-
} | {
|
|
168
|
-
state: 'locked';
|
|
169
|
-
/**
|
|
170
|
-
* One entry per wrapping that could be opened here: an OR of ANDs. Nested
|
|
171
|
-
* because flattening loses the difference between a key openable by a pin
|
|
172
|
-
* OR a passphrase and one demanding both, and those are different prompts.
|
|
173
|
-
* `did` factors are absent — this device supplies them without asking.
|
|
174
|
-
*/
|
|
175
|
-
unlockOptions: Array<Array<SecretFactorKind>>;
|
|
176
|
-
} | {
|
|
177
|
-
state: 'unopenable';
|
|
178
|
-
reason: CredentialUnopenableReason;
|
|
179
|
-
};
|
|
180
|
-
export type CredentialGrantResult = {
|
|
181
|
-
/** The key version the recipient's wrapping sits at. */
|
|
182
|
-
version: number;
|
|
183
|
-
/**
|
|
184
|
-
* False when the recipient already held a wrapping and nothing was written.
|
|
185
|
-
* A caller broadcasting the grant emits its frame either way — that is what
|
|
186
|
-
* makes a repeat grant the retry path for a frame that never arrived.
|
|
187
|
-
*/
|
|
188
|
-
created: boolean;
|
|
189
|
-
};
|
|
190
|
-
export type CredentialManager = {
|
|
191
|
-
/** Factor kinds this device can supply. Empty means it can hold no credentials. */
|
|
192
|
-
availableFactors(): Array<string>;
|
|
193
|
-
/**
|
|
194
|
-
* Open a key and hold its content key in memory.
|
|
195
|
-
*
|
|
196
|
-
* A `did`-only key needs no call: it opens on demand. This is for the ones
|
|
197
|
-
* carrying a factor only the user can supply.
|
|
198
|
-
*/
|
|
199
|
-
unlock(keyID: string, inputs?: FactorInputs): Promise<void>;
|
|
200
|
-
/** Zero the cached content key. TTL eviction does the same thing. */
|
|
201
|
-
lock(keyID: string): void;
|
|
202
|
-
lockAll(): void;
|
|
203
|
-
status(keyID: string, version?: number): Promise<CredentialKeyStatus>;
|
|
204
|
-
/**
|
|
205
|
-
* Add a factor to THIS device's wrapping, leaving every other recipient's
|
|
206
|
-
* alone. Adding a `pin` therefore gates this device and no other, which is the
|
|
207
|
-
* intended granularity.
|
|
208
|
-
*
|
|
209
|
-
* `params.inputs` must carry the secret for every factor that SURVIVES the
|
|
210
|
-
* change, not only the one being added: the new wrapping's key is derived from
|
|
211
|
-
* all of them, and a descriptor holds a salt, never a secret.
|
|
212
|
-
*/
|
|
213
|
-
addFactor(keyID: string, factor: FactorInput, params?: FactorChangeParams): Promise<void>;
|
|
214
|
-
/** Remove a factor kind from this device's wrapping. See `addFactor` on `params.inputs`. */
|
|
215
|
-
removeFactor(keyID: string, kind: string, params?: FactorChangeParams): Promise<void>;
|
|
216
|
-
createKey(params?: CreateKeyParams): Promise<string>;
|
|
217
|
-
/**
|
|
218
|
-
* Retire this key's content key: mint a fresh one at `version + 1`,
|
|
219
|
-
* re-encrypt every entry under it and re-make every wrapping. Returns the new
|
|
220
|
-
* version, which a caller has to carry in the `credential:key-rotate` frame.
|
|
221
|
-
*
|
|
222
|
-
* `keyID` and every `entryID` survive, so documents referencing them do not
|
|
223
|
-
* move. A wrapping survives only if this device can re-make it: `did`-only
|
|
224
|
-
* ones re-make unattended, and one carrying a secret factor needs that secret
|
|
225
|
-
* back in `params.inputs`, because re-making a wrapping derives its KEK from
|
|
226
|
-
* every surviving factor and a descriptor holds a salt, never a secret.
|
|
227
|
-
*
|
|
228
|
-
* NOT atomic. This manager holds a resolved store, not a `StoreProvider`, so
|
|
229
|
-
* it has no transaction to open — a caller wanting all-or-nothing has to build
|
|
230
|
-
* the manager inside one.
|
|
231
|
-
*/
|
|
232
|
-
rotate(keyID: string, params?: RotateKeyParams): Promise<number>;
|
|
233
|
-
/**
|
|
234
|
-
* Address this key to one more recipient: a `did` wrapping over the SAME
|
|
235
|
-
* content key at the key's current version. Every entry already written stays
|
|
236
|
-
* as it is — the recipient opens the key, not a copy of it — and no entry
|
|
237
|
-
* plaintext is touched. Returns the version the wrapping was made at, which a
|
|
238
|
-
* caller has to carry in the `credential:key-grant` frame.
|
|
239
|
-
*
|
|
240
|
-
* A recipient that already holds a wrapping is a no-op, so the version alone
|
|
241
|
-
* cannot tell a fresh grant from a re-grant — `created` is what distinguishes
|
|
242
|
-
* them.
|
|
243
|
-
*
|
|
244
|
-
* `recipientWrappableDID` must be RESOLVABLE, not merely a name: a
|
|
245
|
-
* `did:peer:4` short form carries no document and so no agreement key. Working
|
|
246
|
-
* out which string that is for a given party is a group question, and this
|
|
247
|
-
* package does not know what a group is — so it takes the answer rather than
|
|
248
|
-
* asking.
|
|
249
|
-
*
|
|
250
|
-
* `administer`, and the device must be able to open the key: a locked one
|
|
251
|
-
* fails before the first write rather than persisting half a grant.
|
|
252
|
-
*/
|
|
253
|
-
grant(keyID: string, recipientWrappableDID: string, params?: GrantKeyParams): Promise<CredentialGrantResult>;
|
|
254
|
-
listKeys(): Promise<Array<string>>;
|
|
255
|
-
/** How many wrappings a key carries — one per way of opening it. */
|
|
256
|
-
countWrappings(keyID: string): Promise<number>;
|
|
257
|
-
deleteKey(keyID: string): Promise<void>;
|
|
258
|
-
putEntry(keyID: string, plaintext: Uint8Array): Promise<string>;
|
|
259
|
-
updateEntry(entryID: string, plaintext: Uint8Array): Promise<void>;
|
|
260
|
-
/**
|
|
261
|
-
* As {@link updateEntry}, but never inserts: an entry gone by the time the
|
|
262
|
-
* write lands reports `'absent'` instead of raising. For a writeback racing a
|
|
263
|
-
* concurrent delete, where the caller has no `keyID` to fall back to `putEntry`
|
|
264
|
-
* with and raising would just be a `catch` the caller re-swallows.
|
|
265
|
-
*/
|
|
266
|
-
updateEntryIfPresent(entryID: string, plaintext: Uint8Array): Promise<'updated' | 'absent'>;
|
|
267
|
-
readEntry(entryID: string): Promise<Uint8Array>;
|
|
268
|
-
getEntryProvenance(entryID: string): Promise<CredentialEntryProvenance>;
|
|
269
|
-
/**
|
|
270
|
-
* The RAW `owner_did` off the entry's key row — never normalized. Callers
|
|
271
|
-
* comparing against another DID normalize it themselves; folding it here
|
|
272
|
-
* would make the answer wrong for whichever caller wanted the unfolded form.
|
|
273
|
-
* `null` when the entry, or the key it sits on, is absent.
|
|
274
|
-
*/
|
|
275
|
-
getEntryKeyOwnerDID(entryID: string): Promise<string | null>;
|
|
276
|
-
listEntries(keyID: string): Promise<Array<string>>;
|
|
277
|
-
deleteEntry(entryID: string): Promise<void>;
|
|
278
|
-
/**
|
|
279
|
-
* Move an entry onto another key: decrypt under the one it sits on, re-encrypt
|
|
280
|
-
* under the target's current version, keep the entry id.
|
|
281
|
-
*
|
|
282
|
-
* The key is the sharing unit, so sharing one secret means moving it onto a
|
|
283
|
-
* shared key rather than sharing the key it already sits under, which would
|
|
284
|
-
* share every other entry beneath that one too. The entry id survives because
|
|
285
|
-
* it never embedded a key id — every document referencing it stays valid. The
|
|
286
|
-
* ciphertext does not survive: it is bound to its key, so this is a genuine
|
|
287
|
-
* re-encryption and the target must be open, not merely named.
|
|
288
|
-
*
|
|
289
|
-
* `administer` on BOTH keys. Writing under the target changes what the target
|
|
290
|
-
* holds, and taking an entry out of the source changes what the source holds.
|
|
291
|
-
*/
|
|
292
|
-
reKeyEntry(entryID: string, toKeyID: string, params?: ReKeyEntryParams): Promise<void>;
|
|
293
|
-
};
|
|
1
|
+
import type { CreateCredentialManagerParams, CredentialManager } from './manager-types.js';
|
|
2
|
+
export type { CreateCredentialManagerParams, CreateKeyParams, CredentialEntryAddress, CredentialGrantResult, CredentialKeyStatus, CredentialManager, CredentialUnopenableReason, FactorChangeParams, GrantEntryParams, GrantKeyParams, RecoverRemovedDevicesParams, ReKeyCredentialEntryParams, ReKeyEntryParams, RotateKeyParams, UpdateCredentialEntryIfUnchangedParams, } from './manager-types.js';
|
|
294
3
|
export declare function createCredentialManager(params: CreateCredentialManagerParams): CredentialManager;
|