@kubun/credential 0.17.3 → 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 +16 -6
- package/lib/index.js +8 -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 -1072
- 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/lib/wrappable.d.ts +14 -0
- package/lib/wrappable.js +15 -0
- package/package.json +16 -15
- package/lib/op.d.ts +0 -128
- package/lib/op.js +0 -304
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
import { normalizeDID } from '@kokuin/token';
|
|
2
|
+
import { buildCredentialKey } from './builder.js';
|
|
3
|
+
import { CredentialNotAuthorized, CredentialNotFound, CredentialPinRejected, CredentialRecipientRevoked, CredentialSecretRejected, InvalidCredentialFactors, NoAvailableFactor } from './errors.js';
|
|
4
|
+
import { SECRET_FACTOR_KINDS } from './manager-context.js';
|
|
5
|
+
import { getSuite, isSupportedSuite, SUITE_KUBUN_CRED_1 } from './suite.js';
|
|
6
|
+
import { wrappableDID } from './wrappable.js';
|
|
7
|
+
export function createKeyMethods(ctx, internals) {
|
|
8
|
+
const { store, identity, runtime, authority, decrypting, cache, throttle, state, ensureFloored, nextHLC } = ctx;
|
|
9
|
+
const { putRecord, keyRecord, keyUnique, putWrappingRecord } = internals.records;
|
|
10
|
+
const { isSelfRevoked, satisfiableFactors, loadKey, openKey, rewrapOwn } = internals.open;
|
|
11
|
+
const { checkRemovalRecovery } = internals.removalRecovery;
|
|
12
|
+
const { rotateKey } = internals.rotate;
|
|
13
|
+
return {
|
|
14
|
+
availableFactors () {
|
|
15
|
+
// A passphrase or pin needs no key material, only someone to ask. They are
|
|
16
|
+
// available on every device, including one that cannot decrypt at all.
|
|
17
|
+
return decrypting == null ? [
|
|
18
|
+
...SECRET_FACTOR_KINDS
|
|
19
|
+
] : [
|
|
20
|
+
'did',
|
|
21
|
+
...SECRET_FACTOR_KINDS
|
|
22
|
+
];
|
|
23
|
+
},
|
|
24
|
+
async unlock (keyID, inputs) {
|
|
25
|
+
if (inputs?.pin == null) {
|
|
26
|
+
// Unlock only warms the cache; the returned clone is ours to zero.
|
|
27
|
+
;
|
|
28
|
+
(await openKey(keyID, inputs)).contentKey.fill(0);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
// Refused BEFORE `openKey`, so a throttled attempt never reaches Argon2.
|
|
32
|
+
// Below the KDF the 340ms is paid either way and the counter buys the
|
|
33
|
+
// defender nothing it does not also cost.
|
|
34
|
+
const wait = throttle.retryAfter(keyID);
|
|
35
|
+
if (wait > 0) {
|
|
36
|
+
throw new CredentialPinRejected(keyID, {
|
|
37
|
+
attemptsRemaining: throttle.attemptsRemaining(keyID),
|
|
38
|
+
retryAfter: wait
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
try {
|
|
42
|
+
;
|
|
43
|
+
(await openKey(keyID, inputs)).contentKey.fill(0);
|
|
44
|
+
} catch (error) {
|
|
45
|
+
// Only a rejected secret advances the counter. A key that is simply not
|
|
46
|
+
// addressed to this device, or whose suite is unknown, is not someone
|
|
47
|
+
// guessing — counting it would lock a user out over a fault.
|
|
48
|
+
if (error instanceof CredentialSecretRejected) {
|
|
49
|
+
throttle.recordFailure(keyID);
|
|
50
|
+
throw new CredentialPinRejected(keyID, {
|
|
51
|
+
cause: error,
|
|
52
|
+
attemptsRemaining: throttle.attemptsRemaining(keyID),
|
|
53
|
+
retryAfter: throttle.retryAfter(keyID)
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
throw error;
|
|
57
|
+
}
|
|
58
|
+
throttle.recordSuccess(keyID);
|
|
59
|
+
},
|
|
60
|
+
async addFactor (keyID, factor, params) {
|
|
61
|
+
await checkRemovalRecovery();
|
|
62
|
+
// Ownership, not possession of a wrapping. A rotation has to re-make every
|
|
63
|
+
// retained wrapping at the new version, and a secret factor cannot be
|
|
64
|
+
// re-made without its secret — so a recipient gating its own wrapping of
|
|
65
|
+
// someone else's key would make that key un-rotatable, and revocation is
|
|
66
|
+
// rotation.
|
|
67
|
+
//
|
|
68
|
+
// Asked for the verb rather than only for the secret kinds: a `did` added
|
|
69
|
+
// to a foreign wrapping re-makes unattended, but it is still a change to a
|
|
70
|
+
// key this identity does not administer, and the narrower rule buys the
|
|
71
|
+
// caller nothing it could use.
|
|
72
|
+
const key = await loadKey(keyID);
|
|
73
|
+
if (!await (params?.authority ?? authority).authorize('administer', {
|
|
74
|
+
key,
|
|
75
|
+
store
|
|
76
|
+
})) {
|
|
77
|
+
throw new CredentialNotAuthorized('administer', keyID);
|
|
78
|
+
}
|
|
79
|
+
await rewrapOwn({
|
|
80
|
+
keyID,
|
|
81
|
+
inputs: params?.inputs,
|
|
82
|
+
change: (factors)=>[
|
|
83
|
+
...factors,
|
|
84
|
+
factor
|
|
85
|
+
],
|
|
86
|
+
methods: params?.methods,
|
|
87
|
+
authority: params?.authority ?? authority
|
|
88
|
+
});
|
|
89
|
+
},
|
|
90
|
+
async removeFactor (keyID, kind, params) {
|
|
91
|
+
await checkRemovalRecovery();
|
|
92
|
+
// Dropping a factor (a pin, say) weakens the key exactly as adding one
|
|
93
|
+
// strengthens it, so it is administration too — see `addFactor`.
|
|
94
|
+
const key = await loadKey(keyID);
|
|
95
|
+
if (!await (params?.authority ?? authority).authorize('administer', {
|
|
96
|
+
key,
|
|
97
|
+
store
|
|
98
|
+
})) {
|
|
99
|
+
throw new CredentialNotAuthorized('administer', keyID);
|
|
100
|
+
}
|
|
101
|
+
await rewrapOwn({
|
|
102
|
+
keyID,
|
|
103
|
+
inputs: params?.inputs,
|
|
104
|
+
change: (factors)=>{
|
|
105
|
+
const remaining = factors.filter((factor)=>factor.kind !== kind);
|
|
106
|
+
if (remaining.length === factors.length) {
|
|
107
|
+
throw new CredentialNotFound(`Credential factor "${kind}" on this device's wrapping of`, keyID);
|
|
108
|
+
}
|
|
109
|
+
// Nothing here refuses a removal that would leave a pin without a did,
|
|
110
|
+
// or nothing at all: `suite.wrap` validates the list it is given, so
|
|
111
|
+
// the refusal happens once, where every path reaches it.
|
|
112
|
+
return remaining;
|
|
113
|
+
},
|
|
114
|
+
methods: params?.methods,
|
|
115
|
+
authority: params?.authority ?? authority
|
|
116
|
+
});
|
|
117
|
+
},
|
|
118
|
+
lock (keyID) {
|
|
119
|
+
// Bump before evicting so an `openKey` already past its generation capture
|
|
120
|
+
// sees the change and skips its `cache.set`.
|
|
121
|
+
state.keyGeneration.set(keyID, (state.keyGeneration.get(keyID) ?? 0) + 1);
|
|
122
|
+
cache.evict(keyID);
|
|
123
|
+
},
|
|
124
|
+
lockAll () {
|
|
125
|
+
state.lockAllEpoch += 1;
|
|
126
|
+
cache.evictAll();
|
|
127
|
+
},
|
|
128
|
+
async status (keyID, version) {
|
|
129
|
+
const key = await store.getKey(keyID);
|
|
130
|
+
if (key == null) {
|
|
131
|
+
throw new CredentialNotFound('Credential key', keyID);
|
|
132
|
+
}
|
|
133
|
+
// Reported, not thrown. `loadKey` raises here, which would make the call
|
|
134
|
+
// that exists to say "this device cannot read that key" fail on exactly
|
|
135
|
+
// the key it was asked about.
|
|
136
|
+
if (!isSupportedSuite(key.suite)) {
|
|
137
|
+
return {
|
|
138
|
+
state: 'unopenable',
|
|
139
|
+
reason: 'unsupported-suite'
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
// Defaults to the key's current version; a caller checking a manifest
|
|
143
|
+
// target passes the expected version so a stale wrapping is never
|
|
144
|
+
// reported openable for a newer one, or vice versa.
|
|
145
|
+
const targetVersion = version ?? key.version;
|
|
146
|
+
if (await isSelfRevoked(keyID)) {
|
|
147
|
+
return {
|
|
148
|
+
state: 'unopenable',
|
|
149
|
+
reason: 'revoked'
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
if (cache.has(keyID, targetVersion)) {
|
|
153
|
+
return {
|
|
154
|
+
state: 'unlocked'
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
const wrappings = await store.listWrappings(keyID, targetVersion);
|
|
158
|
+
if (wrappings.length === 0) {
|
|
159
|
+
return {
|
|
160
|
+
state: 'unopenable',
|
|
161
|
+
reason: 'no-wrapping'
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
const unlockOptions = [];
|
|
165
|
+
for (const wrapping of wrappings){
|
|
166
|
+
const satisfiable = satisfiableFactors(wrapping.factors);
|
|
167
|
+
if (satisfiable == null) {
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
// Nothing to ask for: this one opens on the identity alone, so the key
|
|
171
|
+
// needs no interaction however many other wrappings do.
|
|
172
|
+
if (satisfiable.length === 0) {
|
|
173
|
+
return {
|
|
174
|
+
state: 'available'
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
unlockOptions.push(satisfiable);
|
|
178
|
+
}
|
|
179
|
+
return unlockOptions.length > 0 ? {
|
|
180
|
+
state: 'locked',
|
|
181
|
+
unlockOptions
|
|
182
|
+
} : {
|
|
183
|
+
state: 'unopenable',
|
|
184
|
+
reason: 'not-addressed'
|
|
185
|
+
};
|
|
186
|
+
},
|
|
187
|
+
async createKey (createParams = {}) {
|
|
188
|
+
await checkRemovalRecovery();
|
|
189
|
+
const suiteID = createParams.suite ?? SUITE_KUBUN_CRED_1;
|
|
190
|
+
if (createParams.factors != null && createParams.wrappings != null) {
|
|
191
|
+
throw new InvalidCredentialFactors('pass either "factors" or "wrappings", not both');
|
|
192
|
+
}
|
|
193
|
+
// The headless default: one wrapping to this device's wrappable form. Taking
|
|
194
|
+
// it without a decrypting identity would mint a key whose only wrapping
|
|
195
|
+
// this device can never open, so it refuses here rather than at first read.
|
|
196
|
+
const explicit = createParams.wrappings ?? (createParams.factors && [
|
|
197
|
+
createParams.factors
|
|
198
|
+
]);
|
|
199
|
+
const wrappings = explicit ?? [
|
|
200
|
+
[
|
|
201
|
+
{
|
|
202
|
+
kind: 'did',
|
|
203
|
+
recipientDID: wrappableDID(identity)
|
|
204
|
+
}
|
|
205
|
+
]
|
|
206
|
+
];
|
|
207
|
+
if (explicit == null && decrypting == null) {
|
|
208
|
+
throw new NoAvailableFactor('did');
|
|
209
|
+
}
|
|
210
|
+
const built = await buildCredentialKey({
|
|
211
|
+
runtime,
|
|
212
|
+
ownerDID: createParams.ownerDID ?? identity.id,
|
|
213
|
+
suite: suiteID,
|
|
214
|
+
wrappings,
|
|
215
|
+
methods: createParams.methods
|
|
216
|
+
});
|
|
217
|
+
// The wrappings already encapsulate the content key; createKey never reads
|
|
218
|
+
// `built.contentKey` again, so zero it rather than leave the generated
|
|
219
|
+
// secret live until collection.
|
|
220
|
+
built.contentKey.fill(0);
|
|
221
|
+
const keyParams = {
|
|
222
|
+
keyID: built.keyID,
|
|
223
|
+
ownerDID: built.ownerDID,
|
|
224
|
+
suite: built.suite,
|
|
225
|
+
version: built.version,
|
|
226
|
+
state: 'active'
|
|
227
|
+
};
|
|
228
|
+
await ensureFloored();
|
|
229
|
+
// The key-op opens the content-key branch every wrapping here joins, so
|
|
230
|
+
// its label is threaded onto them.
|
|
231
|
+
await store.transaction(async (tx)=>{
|
|
232
|
+
const { branchID, branchRecordID: branchIDRecord } = await keyRecord({
|
|
233
|
+
key: keyParams,
|
|
234
|
+
keyUnique: built.keyUnique,
|
|
235
|
+
hlc: await nextHLC(),
|
|
236
|
+
authority,
|
|
237
|
+
target: tx
|
|
238
|
+
});
|
|
239
|
+
for (const wrapping of built.wrappings){
|
|
240
|
+
await putWrappingRecord({
|
|
241
|
+
wrapping,
|
|
242
|
+
owner: built.ownerDID,
|
|
243
|
+
branchID,
|
|
244
|
+
branchRecordID: branchIDRecord,
|
|
245
|
+
authority,
|
|
246
|
+
target: tx
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
return built.keyID;
|
|
251
|
+
},
|
|
252
|
+
async rotate (keyID, rotateParams) {
|
|
253
|
+
await checkRemovalRecovery();
|
|
254
|
+
return await rotateKey({
|
|
255
|
+
keyID,
|
|
256
|
+
rotateParams
|
|
257
|
+
});
|
|
258
|
+
},
|
|
259
|
+
async revoke (keyID, recipientDID, revokeParams) {
|
|
260
|
+
await checkRemovalRecovery();
|
|
261
|
+
const key = await loadKey(keyID);
|
|
262
|
+
const revocationAuthority = revokeParams?.authority ?? authority;
|
|
263
|
+
if (!await revocationAuthority.authorizeRevocation?.({
|
|
264
|
+
key,
|
|
265
|
+
store
|
|
266
|
+
}) && normalizeDID(key.owner_did) !== normalizeDID(identity.id)) {
|
|
267
|
+
throw new CredentialNotAuthorized('administer', keyID);
|
|
268
|
+
}
|
|
269
|
+
const revokedDID = normalizeDID(recipientDID);
|
|
270
|
+
if ((await store.listRevocations(keyID)).some((row)=>normalizeDID(row.recipient_did) === revokedDID)) {
|
|
271
|
+
return key.version;
|
|
272
|
+
}
|
|
273
|
+
const version = await rotateKey({
|
|
274
|
+
keyID,
|
|
275
|
+
rotateParams: revokeParams,
|
|
276
|
+
revokedDID
|
|
277
|
+
});
|
|
278
|
+
if (revokedDID === normalizeDID(identity.id)) cache.evict(keyID);
|
|
279
|
+
return version;
|
|
280
|
+
},
|
|
281
|
+
async grant (keyID, recipientWrappableDID, params) {
|
|
282
|
+
await checkRemovalRecovery();
|
|
283
|
+
await ensureFloored();
|
|
284
|
+
const key = await loadKey(keyID);
|
|
285
|
+
// Administration, and by a stronger argument than `rotate`'s: this hands
|
|
286
|
+
// the content key to a party that did not have it, which is the one change
|
|
287
|
+
// no later rotation can take back from whoever received it.
|
|
288
|
+
if (!await (params?.authority ?? authority).authorize('administer', {
|
|
289
|
+
key,
|
|
290
|
+
store
|
|
291
|
+
})) {
|
|
292
|
+
throw new CredentialNotAuthorized('administer', keyID);
|
|
293
|
+
}
|
|
294
|
+
// A repeat grant needs no key open or recipient resolution. Its answer is
|
|
295
|
+
// still taken under the same key lock as rotation.
|
|
296
|
+
const alreadyGranted = await store.transaction(async (tx)=>{
|
|
297
|
+
await tx.lockKeyRow(keyID);
|
|
298
|
+
const current = await tx.getKey(keyID);
|
|
299
|
+
if (current == null) throw new CredentialNotFound('Credential key', keyID);
|
|
300
|
+
if ((await tx.listRevocations(keyID)).some((row)=>normalizeDID(row.recipient_did) === normalizeDID(recipientWrappableDID))) {
|
|
301
|
+
throw new CredentialRecipientRevoked(keyID, recipientWrappableDID);
|
|
302
|
+
}
|
|
303
|
+
const addressed = (await tx.listWrappings(keyID, current.version)).some((wrapping)=>wrapping.factors.some((descriptor)=>descriptor.kind === 'did' && typeof descriptor.params.recipient === 'string' && normalizeDID(descriptor.params.recipient) === normalizeDID(recipientWrappableDID)));
|
|
304
|
+
return addressed ? {
|
|
305
|
+
version: current.version,
|
|
306
|
+
created: false
|
|
307
|
+
} : null;
|
|
308
|
+
});
|
|
309
|
+
if (alreadyGranted != null) return alreadyGranted;
|
|
310
|
+
// The key open and recipient key resolution run outside the transaction.
|
|
311
|
+
// The locked read decides whether this speculative wrapping is still at
|
|
312
|
+
// the current version; if rotation committed meanwhile, prepare again.
|
|
313
|
+
for(;;){
|
|
314
|
+
const candidate = await loadKey(keyID);
|
|
315
|
+
const { contentKey, version, branchID } = await openKey(keyID);
|
|
316
|
+
const wrappingID = runtime.getRandomID();
|
|
317
|
+
let wrapped;
|
|
318
|
+
try {
|
|
319
|
+
wrapped = await getSuite(candidate.suite).wrap({
|
|
320
|
+
runtime,
|
|
321
|
+
contentKey,
|
|
322
|
+
factors: [
|
|
323
|
+
{
|
|
324
|
+
kind: 'did',
|
|
325
|
+
recipientDID: recipientWrappableDID
|
|
326
|
+
}
|
|
327
|
+
],
|
|
328
|
+
wrappingID,
|
|
329
|
+
keyID,
|
|
330
|
+
keyVersion: version,
|
|
331
|
+
methods: params?.methods
|
|
332
|
+
});
|
|
333
|
+
} finally{
|
|
334
|
+
contentKey.fill(0);
|
|
335
|
+
}
|
|
336
|
+
const result = await store.transaction(async (tx)=>{
|
|
337
|
+
await tx.lockKeyRow(keyID);
|
|
338
|
+
const current = await tx.getKey(keyID);
|
|
339
|
+
if (current == null) throw new CredentialNotFound('Credential key', keyID);
|
|
340
|
+
if ((await tx.listRevocations(keyID)).some((row)=>normalizeDID(row.recipient_did) === normalizeDID(recipientWrappableDID))) {
|
|
341
|
+
throw new CredentialRecipientRevoked(keyID, recipientWrappableDID);
|
|
342
|
+
}
|
|
343
|
+
if (current.version !== version || current.suite !== candidate.suite) return null;
|
|
344
|
+
const addressed = (await tx.listWrappings(keyID, version)).some((wrapping)=>wrapping.factors.some((descriptor)=>descriptor.kind === 'did' && typeof descriptor.params.recipient === 'string' && normalizeDID(descriptor.params.recipient) === normalizeDID(recipientWrappableDID)));
|
|
345
|
+
if (addressed) return {
|
|
346
|
+
version,
|
|
347
|
+
created: false
|
|
348
|
+
};
|
|
349
|
+
await putWrappingRecord({
|
|
350
|
+
wrapping: {
|
|
351
|
+
wrappingID,
|
|
352
|
+
keyID,
|
|
353
|
+
keyVersion: version,
|
|
354
|
+
factors: wrapped.descriptors,
|
|
355
|
+
iv: wrapped.iv,
|
|
356
|
+
wrappedKey: wrapped.wrappedKey,
|
|
357
|
+
recipientDID: recipientWrappableDID
|
|
358
|
+
},
|
|
359
|
+
owner: current.owner_did,
|
|
360
|
+
branchID,
|
|
361
|
+
authority: params?.authority ?? authority,
|
|
362
|
+
target: tx
|
|
363
|
+
});
|
|
364
|
+
return {
|
|
365
|
+
version,
|
|
366
|
+
created: true
|
|
367
|
+
};
|
|
368
|
+
});
|
|
369
|
+
if (result != null) return result;
|
|
370
|
+
}
|
|
371
|
+
},
|
|
372
|
+
async listKeys () {
|
|
373
|
+
return (await store.listKeys(normalizeDID(identity.id))).map((key)=>key.key_id);
|
|
374
|
+
},
|
|
375
|
+
async countWrappings (keyID) {
|
|
376
|
+
const key = await store.getKey(keyID);
|
|
377
|
+
if (key == null) {
|
|
378
|
+
throw new CredentialNotFound('Credential key', keyID);
|
|
379
|
+
}
|
|
380
|
+
return (await store.listWrappings(keyID, key.version)).length;
|
|
381
|
+
},
|
|
382
|
+
async deleteKey (keyID) {
|
|
383
|
+
await checkRemovalRecovery();
|
|
384
|
+
for(;;){
|
|
385
|
+
const key = await store.getKey(keyID);
|
|
386
|
+
if (key == null) return;
|
|
387
|
+
const unique = await keyUnique(keyID);
|
|
388
|
+
const deletionHLC = await nextHLC();
|
|
389
|
+
const result = await store.transaction(async (tx)=>{
|
|
390
|
+
await tx.lockKeyRow(keyID);
|
|
391
|
+
const current = await tx.getKey(keyID);
|
|
392
|
+
if (current == null) return 'done';
|
|
393
|
+
if (current.version !== key.version) return 'retry';
|
|
394
|
+
await putRecord({
|
|
395
|
+
type: 'credential.key',
|
|
396
|
+
id: keyID,
|
|
397
|
+
owner: current.owner_did,
|
|
398
|
+
hlc: deletionHLC,
|
|
399
|
+
payload: null,
|
|
400
|
+
meta: {
|
|
401
|
+
keyID,
|
|
402
|
+
keyUnique: unique,
|
|
403
|
+
version: current.version
|
|
404
|
+
},
|
|
405
|
+
target: tx
|
|
406
|
+
});
|
|
407
|
+
return 'done';
|
|
408
|
+
});
|
|
409
|
+
if (result === 'done') return;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
};
|
|
413
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { type MethodRegistry } from '@kokuin/token';
|
|
2
|
+
import type { CredentialFactorDescriptor, CredentialKey, CredentialWrappingWithBranch } from '@kubun/store-credential';
|
|
3
|
+
import type { CredentialAuthority } from './authority.js';
|
|
4
|
+
import { type ManagerContext, type ManagerInternals } from './manager-context.js';
|
|
5
|
+
import { type FactorInput, type FactorInputs, type SecretFactorKind } from './suite.js';
|
|
6
|
+
type RewrapOwnParams = {
|
|
7
|
+
keyID: string;
|
|
8
|
+
inputs?: FactorInputs;
|
|
9
|
+
change: (factors: Array<FactorInput>) => Array<FactorInput>;
|
|
10
|
+
methods?: MethodRegistry;
|
|
11
|
+
authority?: CredentialAuthority;
|
|
12
|
+
};
|
|
13
|
+
type DoOpenKeyParams = {
|
|
14
|
+
keyID: string;
|
|
15
|
+
inputs?: FactorInputs;
|
|
16
|
+
genAtStart: number;
|
|
17
|
+
epochAtStart: number;
|
|
18
|
+
atVersion?: number;
|
|
19
|
+
};
|
|
20
|
+
type OpenSpecificBranchParams = {
|
|
21
|
+
keyID: string;
|
|
22
|
+
version: number;
|
|
23
|
+
branchID: string;
|
|
24
|
+
suiteID: number;
|
|
25
|
+
inputs?: FactorInputs;
|
|
26
|
+
};
|
|
27
|
+
export type OpenFunctions = {
|
|
28
|
+
isSelfRevoked: (keyID: string) => Promise<boolean>;
|
|
29
|
+
satisfiableFactors: (descriptors: Array<CredentialFactorDescriptor>) => Array<SecretFactorKind> | null;
|
|
30
|
+
ownWrapping: (keyID: string, keyVersion: number) => Promise<CredentialWrappingWithBranch>;
|
|
31
|
+
factorsFromDescriptors: (descriptors: Array<CredentialFactorDescriptor>, inputs: FactorInputs | undefined) => Array<FactorInput>;
|
|
32
|
+
loadKey: (keyID: string, atVersion?: number) => Promise<CredentialKey>;
|
|
33
|
+
doOpenKey: (params: DoOpenKeyParams) => Promise<OpenedKey>;
|
|
34
|
+
openKey: (keyID: string, inputs?: FactorInputs, atVersion?: number) => Promise<OpenedKey>;
|
|
35
|
+
openSpecificBranch: (params: OpenSpecificBranchParams) => Promise<Uint8Array | null>;
|
|
36
|
+
rewrapOwn: (params: RewrapOwnParams) => Promise<void>;
|
|
37
|
+
hasSuppliedInputs: (inputs?: FactorInputs) => boolean;
|
|
38
|
+
};
|
|
39
|
+
export type OpenedKey = {
|
|
40
|
+
contentKey: Uint8Array;
|
|
41
|
+
version: number;
|
|
42
|
+
branchID: string;
|
|
43
|
+
};
|
|
44
|
+
export declare function createOpenFunctions(ctx: ManagerContext, internals: ManagerInternals): OpenFunctions;
|
|
45
|
+
export {};
|