@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
package/lib/authority.d.ts
CHANGED
|
@@ -11,6 +11,8 @@ export type CredentialAuthorityParams = {
|
|
|
11
11
|
store: CredentialStoreAPI;
|
|
12
12
|
};
|
|
13
13
|
export type CredentialAuthority = {
|
|
14
|
+
/** Administer proof to bind into records signed for a non-controller owner. */
|
|
15
|
+
delegationTokens?: Array<string>;
|
|
14
16
|
/**
|
|
15
17
|
* The DID a key minted by this viewer belongs to.
|
|
16
18
|
*
|
|
@@ -19,6 +21,8 @@ export type CredentialAuthority = {
|
|
|
19
21
|
*/
|
|
20
22
|
ownerForNewKey(): Promise<string>;
|
|
21
23
|
authorize(authorization: CredentialAuthorization, params: CredentialAuthorityParams): Promise<boolean>;
|
|
24
|
+
/** Revocation is reserved for the owner or a current controller-head device. */
|
|
25
|
+
authorizeRevocation?(params: CredentialAuthorityParams): Promise<boolean>;
|
|
22
26
|
};
|
|
23
27
|
/**
|
|
24
28
|
* The stand-in until ownership is a controller/agent capability chain: the viewer
|
package/lib/authority.js
CHANGED
|
@@ -10,6 +10,9 @@ import { normalizeDID } from '@kokuin/token';
|
|
|
10
10
|
async ownerForNewKey () {
|
|
11
11
|
return viewerDID;
|
|
12
12
|
},
|
|
13
|
+
async authorizeRevocation ({ key }) {
|
|
14
|
+
return normalizeDID(key.owner_did) === viewerDID;
|
|
15
|
+
},
|
|
13
16
|
async authorize (authorization, { key, store }) {
|
|
14
17
|
if (normalizeDID(key.owner_did) === viewerDID) {
|
|
15
18
|
return true;
|
package/lib/branch.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** Order branches by their introducing record op hash, then by HLC. */
|
|
2
|
+
export declare function compareBranchIDs(a: string, b: string): number;
|
|
3
|
+
/** Choose the same branch regardless of input order. */
|
|
4
|
+
export declare function selectWinningBranch(branchIDs: ReadonlyArray<string>): string;
|
package/lib/branch.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/** Split an authenticated branch label at the last colon because HLCs contain colons. */ function splitBranchID(branchID) {
|
|
2
|
+
const boundary = branchID.lastIndexOf(':');
|
|
3
|
+
return boundary < 0 ? {
|
|
4
|
+
hlc: branchID,
|
|
5
|
+
opHash: ''
|
|
6
|
+
} : {
|
|
7
|
+
hlc: branchID.slice(0, boundary),
|
|
8
|
+
opHash: branchID.slice(boundary + 1)
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
/** Order branches by their introducing record op hash, then by HLC. */ export function compareBranchIDs(a, b) {
|
|
12
|
+
const left = splitBranchID(a);
|
|
13
|
+
const right = splitBranchID(b);
|
|
14
|
+
if (left.opHash !== right.opHash) {
|
|
15
|
+
return left.opHash < right.opHash ? -1 : 1;
|
|
16
|
+
}
|
|
17
|
+
return left.hlc < right.hlc ? -1 : left.hlc > right.hlc ? 1 : 0;
|
|
18
|
+
}
|
|
19
|
+
/** Choose the same branch regardless of input order. */ export function selectWinningBranch(branchIDs) {
|
|
20
|
+
if (branchIDs.length === 0) {
|
|
21
|
+
throw new RangeError('selectWinningBranch requires at least one branchID');
|
|
22
|
+
}
|
|
23
|
+
return branchIDs.reduce((winner, candidate)=>compareBranchIDs(candidate, winner) > 0 ? candidate : winner);
|
|
24
|
+
}
|
package/lib/builder.d.ts
CHANGED
|
@@ -39,6 +39,11 @@ export type BuiltWrapping = {
|
|
|
39
39
|
};
|
|
40
40
|
export type BuiltKey = CredentialKeyMaterial & {
|
|
41
41
|
wrappings: Array<BuiltWrapping>;
|
|
42
|
+
/**
|
|
43
|
+
* The random half of `keyID`, base64url. The id is the digest of the owner and
|
|
44
|
+
* this value, so a key record carrying it proves its id names this owner's key.
|
|
45
|
+
*/
|
|
46
|
+
keyUnique: string;
|
|
42
47
|
};
|
|
43
48
|
export type BuildKeyParams = {
|
|
44
49
|
runtime: Runtime;
|
package/lib/builder.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { normalizeDID } from '@kokuin/token';
|
|
2
2
|
import { CredentialEntryID, CredentialKeyID } from '@kubun/id';
|
|
3
|
+
import { toB64U } from '@sozai/codec';
|
|
3
4
|
import { InvalidCredentialFactors } from './errors.js';
|
|
4
5
|
import { getSuite, SUITE_KUBUN_CRED_1 } from './suite.js';
|
|
5
6
|
/**
|
|
@@ -18,7 +19,8 @@ import { getSuite, SUITE_KUBUN_CRED_1 } from './suite.js';
|
|
|
18
19
|
const suite = getSuite(suiteID);
|
|
19
20
|
const ownerDID = normalizeDID(params.ownerDID);
|
|
20
21
|
const version = 1;
|
|
21
|
-
const
|
|
22
|
+
const unique = runtime.getRandomValues(new Uint8Array(16));
|
|
23
|
+
const keyID = CredentialKeyID.create(ownerDID, unique).toString();
|
|
22
24
|
const contentKey = suite.generateContentKey(runtime);
|
|
23
25
|
const wrappings = [];
|
|
24
26
|
for (const factors of params.wrappings){
|
|
@@ -39,10 +41,9 @@ import { getSuite, SUITE_KUBUN_CRED_1 } from './suite.js';
|
|
|
39
41
|
factors: descriptors,
|
|
40
42
|
iv,
|
|
41
43
|
wrappedKey,
|
|
42
|
-
//
|
|
43
|
-
//
|
|
44
|
-
|
|
45
|
-
recipientDID: factors.length === 1 && factors[0]?.kind === 'did' ? factors[0].recipientDID : null
|
|
44
|
+
// Address delivery to the sole DID factor even when a PIN or passphrase
|
|
45
|
+
// is also required to open it. More than one DID names no sole recipient.
|
|
46
|
+
recipientDID: factors.filter((factor)=>factor.kind === 'did').length === 1 ? factors.find((factor)=>factor.kind === 'did')?.recipientDID ?? null : null
|
|
46
47
|
});
|
|
47
48
|
}
|
|
48
49
|
return {
|
|
@@ -51,7 +52,8 @@ import { getSuite, SUITE_KUBUN_CRED_1 } from './suite.js';
|
|
|
51
52
|
suite: suiteID,
|
|
52
53
|
version,
|
|
53
54
|
contentKey,
|
|
54
|
-
wrappings
|
|
55
|
+
wrappings,
|
|
56
|
+
keyUnique: toB64U(unique)
|
|
55
57
|
};
|
|
56
58
|
}
|
|
57
59
|
/** Encrypt one value under a key already built or already open. */ export function buildCredentialEntry(params) {
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { fromB64U, toB64U } from '@sozai/codec';
|
|
2
|
+
/** Check that a compact token has exactly the spelling produced by its signer. */ export function isCanonicalCompactToken(token) {
|
|
3
|
+
const segments = token.split('.');
|
|
4
|
+
if (segments.length !== 3) return false;
|
|
5
|
+
try {
|
|
6
|
+
return segments.every((segment)=>toB64U(fromB64U(segment)) === segment);
|
|
7
|
+
} catch {
|
|
8
|
+
return false;
|
|
9
|
+
}
|
|
10
|
+
}
|
package/lib/errors.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ import type { CredentialAuthorization } from './authority.js';
|
|
|
6
6
|
* without deciding what a caller is meant to match on. Messages are for people
|
|
7
7
|
* and change; these do not.
|
|
8
8
|
*/
|
|
9
|
-
export type CredentialErrorCode = 'CREDENTIAL_ALIAS_ALREADY_BOUND' | 'CREDENTIAL_BIND_NOT_AUTHORIZED' | 'CREDENTIAL_ENTRY_ALREADY_ALIASED' | 'CREDENTIAL_ERROR' | 'CREDENTIAL_FACTOR_UNAVAILABLE' | 'CREDENTIAL_FACTORS_INVALID' | 'CREDENTIAL_INVALID_ENTRY' | 'CREDENTIAL_KEY_LOCKED' | 'CREDENTIAL_KEY_VERSION_MISMATCH' | 'CREDENTIAL_NOT_AUTHORIZED' | 'CREDENTIAL_NOT_FOUND' | 'CREDENTIAL_PIN_REJECTED' | 'CREDENTIAL_SECRET_REJECTED' | 'CREDENTIAL_SUPERSEDED_BRANCH' | 'CREDENTIAL_SUITE_UNSUPPORTED' | 'CREDENTIAL_TAMPERED' | 'CREDENTIAL_UNOPENABLE' | 'CREDENTIAL_WRAPPING_AMBIGUOUS';
|
|
9
|
+
export type CredentialErrorCode = 'CREDENTIAL_ALIAS_ALREADY_BOUND' | 'CREDENTIAL_BIND_NOT_AUTHORIZED' | 'CREDENTIAL_ENTRY_ALREADY_ALIASED' | 'CREDENTIAL_ENTRY_CONFLICT' | 'CREDENTIAL_ENTRY_OWNER_MISMATCH' | 'CREDENTIAL_ERROR' | 'CREDENTIAL_FACTOR_UNAVAILABLE' | 'CREDENTIAL_FACTORS_INVALID' | 'CREDENTIAL_GRANT_COPY_UNAVAILABLE' | 'CREDENTIAL_GRANT_OWNER_AMBIGUOUS' | 'CREDENTIAL_GRANT_REHOME_NOT_AUTHORIZED' | 'CREDENTIAL_GRANT_ACCOUNT_MISMATCH' | 'CREDENTIAL_INVALID_ENTRY' | 'CREDENTIAL_KEY_LOCKED' | 'CREDENTIAL_KEY_VERSION_MISMATCH' | 'CREDENTIAL_MANAGER_STORE_MISMATCH' | 'CREDENTIAL_NOT_AUTHORIZED' | 'CREDENTIAL_NOT_FOUND' | 'CREDENTIAL_REPAIR_PENDING' | 'CREDENTIAL_RECIPIENT_REVOKED' | 'CREDENTIAL_PIN_REJECTED' | 'CREDENTIAL_SECRET_REJECTED' | 'CREDENTIAL_SUPERSEDED_BRANCH' | 'CREDENTIAL_SUITE_UNSUPPORTED' | 'CREDENTIAL_TAMPERED' | 'CREDENTIAL_UNOPENABLE' | 'CREDENTIAL_WRAPPING_AMBIGUOUS';
|
|
10
10
|
export type CredentialErrorOptions = ErrorOptions & {
|
|
11
11
|
code: CredentialErrorCode;
|
|
12
12
|
};
|
|
@@ -28,6 +28,10 @@ export declare class CredentialError extends Error {
|
|
|
28
28
|
constructor(message: string, options: CredentialErrorOptions);
|
|
29
29
|
get code(): CredentialErrorCode;
|
|
30
30
|
}
|
|
31
|
+
/** A credential manager was built over a different store than the write transaction. */
|
|
32
|
+
export declare class CredentialManagerStoreMismatch extends CredentialError {
|
|
33
|
+
constructor();
|
|
34
|
+
}
|
|
31
35
|
/** A wrapping needs a factor kind this device cannot supply. */
|
|
32
36
|
export declare class NoAvailableFactor extends CredentialError {
|
|
33
37
|
#private;
|
|
@@ -72,6 +76,12 @@ export declare class CredentialSupersededBranch extends CredentialError {
|
|
|
72
76
|
get entryBranchID(): string;
|
|
73
77
|
get winnerBranchID(): string;
|
|
74
78
|
}
|
|
79
|
+
/** A fork has retained data, but this device must retry after a peer repairs it. */
|
|
80
|
+
export declare class CredentialRepairPending extends CredentialError {
|
|
81
|
+
#private;
|
|
82
|
+
constructor(keyID: string);
|
|
83
|
+
get keyID(): string;
|
|
84
|
+
}
|
|
75
85
|
/**
|
|
76
86
|
* A wrapping carrying a user-supplied factor did not authenticate.
|
|
77
87
|
*
|
|
@@ -120,6 +130,10 @@ export declare class CredentialNotAuthorized extends CredentialError {
|
|
|
120
130
|
constructor(authorization: CredentialAuthorization, keyID: string);
|
|
121
131
|
get authorization(): CredentialAuthorization;
|
|
122
132
|
}
|
|
133
|
+
/** A key-specific revocation prevents this recipient from receiving another wrapping. */
|
|
134
|
+
export declare class CredentialRecipientRevoked extends CredentialError {
|
|
135
|
+
constructor(keyID: string, recipientDID: string);
|
|
136
|
+
}
|
|
123
137
|
/** No key, wrapping or entry under that id. */
|
|
124
138
|
export declare class CredentialNotFound extends CredentialError {
|
|
125
139
|
constructor(what: string, id: string);
|
|
@@ -155,6 +169,30 @@ export declare class CredentialKeyLocked extends CredentialError {
|
|
|
155
169
|
export declare class CredentialKeyVersionMismatch extends CredentialError {
|
|
156
170
|
constructor(entryID: string, entryVersion: number, keyVersion: number);
|
|
157
171
|
}
|
|
172
|
+
/** An entry cannot move to a key belonging to another owner. */
|
|
173
|
+
export declare class CredentialEntryOwnerMismatch extends CredentialError {
|
|
174
|
+
constructor(entryID: string, sourceOwnerDID: string, targetOwnerDID: string);
|
|
175
|
+
}
|
|
176
|
+
/** A conditional entry write could not settle after repeated key changes. */
|
|
177
|
+
export declare class CredentialEntryConflict extends CredentialError {
|
|
178
|
+
constructor(entryID: string);
|
|
179
|
+
}
|
|
180
|
+
/** The destination has no addressable local live copy of the grant it was asked to adopt. */
|
|
181
|
+
export declare class CredentialGrantCopyUnavailable extends CredentialError {
|
|
182
|
+
constructor(providerName: string, ownerDID: string, fromDeviceDID: string);
|
|
183
|
+
}
|
|
184
|
+
/** A grant ID has live rows under more than one owner and no source was selected. */
|
|
185
|
+
export declare class CredentialGrantOwnerAmbiguous extends CredentialError {
|
|
186
|
+
constructor(entryID: string, owners: ReadonlyArray<string>);
|
|
187
|
+
}
|
|
188
|
+
/** Replacing a grant requires authority over every old and requested key owner. */
|
|
189
|
+
export declare class CredentialGrantRehomeNotAuthorized extends CredentialError {
|
|
190
|
+
constructor(entryID: string);
|
|
191
|
+
}
|
|
192
|
+
/** A selected grant belongs to another account or uses an unrelated key owner. */
|
|
193
|
+
export declare class CredentialGrantAccountMismatch extends CredentialError {
|
|
194
|
+
constructor(providerName: string, ownerDID: string);
|
|
195
|
+
}
|
|
158
196
|
/**
|
|
159
197
|
* Every wrapping for the key was tried and none opened.
|
|
160
198
|
*
|
package/lib/errors.js
CHANGED
|
@@ -21,6 +21,14 @@
|
|
|
21
21
|
return this.#code;
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
|
+
/** A credential manager was built over a different store than the write transaction. */ export class CredentialManagerStoreMismatch extends CredentialError {
|
|
25
|
+
constructor(){
|
|
26
|
+
super('Credential manager is not bound to the transaction credential store', {
|
|
27
|
+
code: 'CREDENTIAL_MANAGER_STORE_MISMATCH'
|
|
28
|
+
});
|
|
29
|
+
this.name = 'CredentialManagerStoreMismatch';
|
|
30
|
+
}
|
|
31
|
+
}
|
|
24
32
|
/** A wrapping needs a factor kind this device cannot supply. */ export class NoAvailableFactor extends CredentialError {
|
|
25
33
|
#kind;
|
|
26
34
|
constructor(kind){
|
|
@@ -103,6 +111,19 @@
|
|
|
103
111
|
return this.#winnerBranchID;
|
|
104
112
|
}
|
|
105
113
|
}
|
|
114
|
+
/** A fork has retained data, but this device must retry after a peer repairs it. */ export class CredentialRepairPending extends CredentialError {
|
|
115
|
+
#keyID;
|
|
116
|
+
constructor(keyID){
|
|
117
|
+
super(`Credential fork repair pending for key ${keyID}`, {
|
|
118
|
+
code: 'CREDENTIAL_REPAIR_PENDING'
|
|
119
|
+
});
|
|
120
|
+
this.name = 'CredentialRepairPending';
|
|
121
|
+
this.#keyID = keyID;
|
|
122
|
+
}
|
|
123
|
+
get keyID() {
|
|
124
|
+
return this.#keyID;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
106
127
|
/**
|
|
107
128
|
* A wrapping carrying a user-supplied factor did not authenticate.
|
|
108
129
|
*
|
|
@@ -173,6 +194,14 @@
|
|
|
173
194
|
return this.#authorization;
|
|
174
195
|
}
|
|
175
196
|
}
|
|
197
|
+
/** A key-specific revocation prevents this recipient from receiving another wrapping. */ export class CredentialRecipientRevoked extends CredentialError {
|
|
198
|
+
constructor(keyID, recipientDID){
|
|
199
|
+
super(`Recipient ${recipientDID} is revoked for credential key ${keyID}`, {
|
|
200
|
+
code: 'CREDENTIAL_RECIPIENT_REVOKED'
|
|
201
|
+
});
|
|
202
|
+
this.name = 'CredentialRecipientRevoked';
|
|
203
|
+
}
|
|
204
|
+
}
|
|
176
205
|
/** No key, wrapping or entry under that id. */ export class CredentialNotFound extends CredentialError {
|
|
177
206
|
constructor(what, id){
|
|
178
207
|
super(`${what} not found: ${id}`, {
|
|
@@ -227,6 +256,54 @@
|
|
|
227
256
|
this.name = 'CredentialKeyVersionMismatch';
|
|
228
257
|
}
|
|
229
258
|
}
|
|
259
|
+
/** An entry cannot move to a key belonging to another owner. */ export class CredentialEntryOwnerMismatch extends CredentialError {
|
|
260
|
+
constructor(entryID, sourceOwnerDID, targetOwnerDID){
|
|
261
|
+
super(`Credential entry ${entryID} belongs to ${sourceOwnerDID} and cannot move to a key owned by ${targetOwnerDID}`, {
|
|
262
|
+
code: 'CREDENTIAL_ENTRY_OWNER_MISMATCH'
|
|
263
|
+
});
|
|
264
|
+
this.name = 'CredentialEntryOwnerMismatch';
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
/** A conditional entry write could not settle after repeated key changes. */ export class CredentialEntryConflict extends CredentialError {
|
|
268
|
+
constructor(entryID){
|
|
269
|
+
super(`Credential entry ${entryID} changed during the write; retry the operation`, {
|
|
270
|
+
code: 'CREDENTIAL_ENTRY_CONFLICT'
|
|
271
|
+
});
|
|
272
|
+
this.name = 'CredentialEntryConflict';
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
/** The destination has no addressable local live copy of the grant it was asked to adopt. */ export class CredentialGrantCopyUnavailable extends CredentialError {
|
|
276
|
+
constructor(providerName, ownerDID, fromDeviceDID){
|
|
277
|
+
super(`No addressable local copy of ${providerName} grant for ${ownerDID} from device ${fromDeviceDID} is available for adoption`, {
|
|
278
|
+
code: 'CREDENTIAL_GRANT_COPY_UNAVAILABLE'
|
|
279
|
+
});
|
|
280
|
+
this.name = 'CredentialGrantCopyUnavailable';
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
/** A grant ID has live rows under more than one owner and no source was selected. */ export class CredentialGrantOwnerAmbiguous extends CredentialError {
|
|
284
|
+
constructor(entryID, owners){
|
|
285
|
+
super(`Credential grant ${entryID} has multiple source owners: ${owners.join(', ')}`, {
|
|
286
|
+
code: 'CREDENTIAL_GRANT_OWNER_AMBIGUOUS'
|
|
287
|
+
});
|
|
288
|
+
this.name = 'CredentialGrantOwnerAmbiguous';
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
/** Replacing a grant requires authority over every old and requested key owner. */ export class CredentialGrantRehomeNotAuthorized extends CredentialError {
|
|
292
|
+
constructor(entryID){
|
|
293
|
+
super(`Cannot replace credential grant ${entryID} without authority over its key owners`, {
|
|
294
|
+
code: 'CREDENTIAL_GRANT_REHOME_NOT_AUTHORIZED'
|
|
295
|
+
});
|
|
296
|
+
this.name = 'CredentialGrantRehomeNotAuthorized';
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
/** A selected grant belongs to another account or uses an unrelated key owner. */ export class CredentialGrantAccountMismatch extends CredentialError {
|
|
300
|
+
constructor(providerName, ownerDID){
|
|
301
|
+
super(`The selected ${providerName} grant does not belong to ${ownerDID}`, {
|
|
302
|
+
code: 'CREDENTIAL_GRANT_ACCOUNT_MISMATCH'
|
|
303
|
+
});
|
|
304
|
+
this.name = 'CredentialGrantAccountMismatch';
|
|
305
|
+
}
|
|
306
|
+
}
|
|
230
307
|
/**
|
|
231
308
|
* Every wrapping for the key was tried and none opened.
|
|
232
309
|
*
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { CredentialManager } from './manager.js';
|
|
2
|
+
import type { FactorInputs } from './suite.js';
|
|
3
|
+
export type ForkRepairResult = {
|
|
4
|
+
status: 'rotated';
|
|
5
|
+
version: number;
|
|
6
|
+
} | {
|
|
7
|
+
status: 'not-needed';
|
|
8
|
+
} | {
|
|
9
|
+
status: 'pending';
|
|
10
|
+
};
|
|
11
|
+
export type ForkRepairAPI = {
|
|
12
|
+
repairFork(keyID: string, inputs?: FactorInputs): Promise<ForkRepairResult>;
|
|
13
|
+
};
|
|
14
|
+
/** The manager owns signing authority and its shared clock; use that same instance for repair. */
|
|
15
|
+
export declare function repairCredentialFork(params: {
|
|
16
|
+
keyID: string;
|
|
17
|
+
manager: CredentialManager;
|
|
18
|
+
inputs?: FactorInputs;
|
|
19
|
+
}): Promise<ForkRepairResult>;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type MethodRegistry, type SigningIdentity } from '@kokuin/token';
|
|
2
|
+
/**
|
|
3
|
+
* What a grantor signs when handing a credential-wrapping op to a recipient:
|
|
4
|
+
* binds the op to a monotonic refresh sequence. Domain separation from other
|
|
5
|
+
* signed payloads comes from the `kind` marker plus the structural check in
|
|
6
|
+
* `verifyGrantorAuthority` -- `signToken` itself has no domain override.
|
|
7
|
+
*/
|
|
8
|
+
export type GrantorAuthorityClaim = {
|
|
9
|
+
kind: 'credential/grantor-authority';
|
|
10
|
+
wrappingOpHash: string;
|
|
11
|
+
refreshSeq: number;
|
|
12
|
+
grantorDID: string;
|
|
13
|
+
ownerDID: string;
|
|
14
|
+
recipientDID: string;
|
|
15
|
+
delegationTokens: Array<string>;
|
|
16
|
+
};
|
|
17
|
+
/** The envelope carried on the wire and persisted is the signed token string itself. */
|
|
18
|
+
export type GrantorAuthorityEnvelope = string;
|
|
19
|
+
export declare function signGrantorAuthority(identity: SigningIdentity, claim: Omit<GrantorAuthorityClaim, 'kind' | 'grantorDID'>): Promise<GrantorAuthorityEnvelope>;
|
|
20
|
+
/**
|
|
21
|
+
* Verify a grantor-authority envelope and extract its claim. Returns `null`
|
|
22
|
+
* (never throws) when the token is unparseable, unsigned (`alg: 'none'`), or
|
|
23
|
+
* structurally not a grantor-authority claim -- `grantorDID` is trusted only
|
|
24
|
+
* from the verified `iss`, never from a same-named field in the payload.
|
|
25
|
+
*/
|
|
26
|
+
export declare function verifyGrantorAuthority(envelope: GrantorAuthorityEnvelope, methods?: MethodRegistry): Promise<GrantorAuthorityClaim | null>;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { isVerifiedToken, normalizeDID, stringifyToken, verifyToken } from '@kokuin/token';
|
|
2
|
+
export async function signGrantorAuthority(identity, claim) {
|
|
3
|
+
const fullClaim = {
|
|
4
|
+
kind: 'credential/grantor-authority',
|
|
5
|
+
grantorDID: identity.id,
|
|
6
|
+
...claim
|
|
7
|
+
};
|
|
8
|
+
const signed = await identity.signToken(fullClaim, {
|
|
9
|
+
embedLongForm: true
|
|
10
|
+
});
|
|
11
|
+
return stringifyToken(signed);
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Verify a grantor-authority envelope and extract its claim. Returns `null`
|
|
15
|
+
* (never throws) when the token is unparseable, unsigned (`alg: 'none'`), or
|
|
16
|
+
* structurally not a grantor-authority claim -- `grantorDID` is trusted only
|
|
17
|
+
* from the verified `iss`, never from a same-named field in the payload.
|
|
18
|
+
*/ export async function verifyGrantorAuthority(envelope, methods) {
|
|
19
|
+
let verified;
|
|
20
|
+
try {
|
|
21
|
+
verified = await verifyToken(envelope, {
|
|
22
|
+
methods
|
|
23
|
+
});
|
|
24
|
+
} catch {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
if (!isVerifiedToken(verified)) {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
const { iss, kind, wrappingOpHash, refreshSeq, ownerDID, recipientDID, delegationTokens } = verified.payload;
|
|
31
|
+
if (kind !== 'credential/grantor-authority' || typeof wrappingOpHash !== 'string' || typeof refreshSeq !== 'number' || typeof ownerDID !== 'string' || typeof recipientDID !== 'string' || !Array.isArray(delegationTokens) || !delegationTokens.every((token)=>typeof token === 'string')) {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
return {
|
|
35
|
+
kind: 'credential/grantor-authority',
|
|
36
|
+
wrappingOpHash,
|
|
37
|
+
refreshSeq,
|
|
38
|
+
grantorDID: normalizeDID(iss),
|
|
39
|
+
ownerDID,
|
|
40
|
+
recipientDID,
|
|
41
|
+
delegationTokens
|
|
42
|
+
};
|
|
43
|
+
}
|
package/lib/index.d.ts
CHANGED
|
@@ -1,26 +1,35 @@
|
|
|
1
|
-
export type {
|
|
1
|
+
export type { AdoptGrantParams, AdoptGrantResult, GrantAddress } from '@kubun/credential-types';
|
|
2
|
+
export type { AdoptGrantRecoveryParams, CreateCredentialAPIParams, CreateCredentialParams, CredentialAPI, } from './api.js';
|
|
2
3
|
export { createCredentialAPI } from './api.js';
|
|
3
4
|
export type { CredentialAuthority, CredentialAuthorityParams, CredentialAuthorization, } from './authority.js';
|
|
4
5
|
export { createDeviceAuthority } from './authority.js';
|
|
6
|
+
export { compareBranchIDs, selectWinningBranch } from './branch.js';
|
|
5
7
|
export type { BuildEntryParams, BuildKeyParams, BuiltEntry, BuiltKey, BuiltWrapping, CredentialKeyMaterial, } from './builder.js';
|
|
6
8
|
export { buildCredentialEntry, buildCredentialKey } from './builder.js';
|
|
9
|
+
export { isCanonicalCompactToken } from './canonical-token.js';
|
|
7
10
|
export type { CredentialEntryDigestInput, CredentialKeyDigestInput, CredentialWrappingDigestInput, } from './digest.js';
|
|
8
11
|
export { credentialEntriesDigest, credentialEntryDigest, credentialKeyDigest, credentialWrappingDigest, digestParts, } from './digest.js';
|
|
9
12
|
export type { CredentialErrorCode, CredentialErrorOptions } from './errors.js';
|
|
10
|
-
export { AmbiguousCredentialWrapping, CredentialAliasAlreadyBound, CredentialBindNotAuthorized, CredentialEntryAlreadyAliased, CredentialError, CredentialInvalidEntry, CredentialKeyLocked, CredentialKeyVersionMismatch, CredentialNotAuthorized, CredentialNotFound, CredentialPinRejected, CredentialSecretRejected, CredentialSupersededBranch, CredentialTampered, CredentialUnopenable, InvalidCredentialFactors, NoAvailableFactor, UnsupportedCredentialSuite, } from './errors.js';
|
|
11
|
-
export type {
|
|
13
|
+
export { AmbiguousCredentialWrapping, CredentialAliasAlreadyBound, CredentialBindNotAuthorized, CredentialEntryAlreadyAliased, CredentialEntryConflict, CredentialEntryOwnerMismatch, CredentialError, CredentialGrantAccountMismatch, CredentialGrantCopyUnavailable, CredentialGrantOwnerAmbiguous, CredentialGrantRehomeNotAuthorized, CredentialInvalidEntry, CredentialKeyLocked, CredentialKeyVersionMismatch, CredentialManagerStoreMismatch, CredentialNotAuthorized, CredentialNotFound, CredentialPinRejected, CredentialRecipientRevoked, CredentialRepairPending, CredentialSecretRejected, CredentialSupersededBranch, CredentialTampered, CredentialUnopenable, InvalidCredentialFactors, NoAvailableFactor, UnsupportedCredentialSuite, } from './errors.js';
|
|
14
|
+
export type { ForkRepairAPI, ForkRepairResult } from './fork-repair.js';
|
|
15
|
+
export { repairCredentialFork } from './fork-repair.js';
|
|
16
|
+
export type { GrantorAuthorityClaim, GrantorAuthorityEnvelope } from './grantor-authority.js';
|
|
17
|
+
export { signGrantorAuthority, verifyGrantorAuthority } from './grantor-authority.js';
|
|
18
|
+
export type { CreateCredentialManagerParams, CreateKeyParams, CredentialEntryAddress, CredentialGrantResult, CredentialKeyStatus, CredentialManager, CredentialUnopenableReason, FactorChangeParams, GrantEntryParams, GrantKeyParams, RecoverRemovedDevicesParams, ReKeyCredentialEntryParams, ReKeyEntryParams, RotateKeyParams, UpdateCredentialEntryIfUnchangedParams, } from './manager.js';
|
|
12
19
|
export { createCredentialManager } from './manager.js';
|
|
13
|
-
export type {
|
|
14
|
-
export {
|
|
20
|
+
export type { CredentialManifestClaim, CredentialManifestEnvelope } from './manifest-token.js';
|
|
21
|
+
export { signCredentialManifest, verifyCredentialManifest } from './manifest-token.js';
|
|
15
22
|
export type { PinThrottle, PinThrottleParams } from './pin-throttle.js';
|
|
16
23
|
export { createPinThrottle, DEFAULT_BASE_DELAY, DEFAULT_FREE_ATTEMPTS, DEFAULT_MAX_DELAY, } from './pin-throttle.js';
|
|
17
24
|
export type { CredentialEntryProvenance } from './provenance.js';
|
|
18
25
|
export { entryProvenanceFields } from './provenance.js';
|
|
19
26
|
export type { DBCredentialProviderParams } from './provider.js';
|
|
20
27
|
export { DBCredentialProvider, deserializeCredential, serializeCredential } from './provider.js';
|
|
28
|
+
export type { CredentialRecordType, CredentialRecordTypeDeps } from './record-types.js';
|
|
29
|
+
export { CredentialRecordHookError, credentialGrantEntryID, credentialHolderTypes, credentialKeyVersionReader, credentialProofAuthorizer, credentialRecordTypes, credentialStoreForHooks, } from './record-types.js';
|
|
21
30
|
export type { RefreshEvents, RefreshedEvent, RefreshFailedEvent, RefreshFailureCategory, RefreshingCredentialProviderParams, RefreshOutcome, } from './refreshing-credential-provider.js';
|
|
22
31
|
export { RefreshingCredentialProvider } from './refreshing-credential-provider.js';
|
|
23
|
-
export type { Argon2Cost, CredentialSuite, FactorInput, FactorInputs, SecretFactorKind, } from './suite.js';
|
|
32
|
+
export type { Argon2Cost, CredentialSuite, EncryptEntryResult, FactorInput, FactorInputs, SecretFactorKind, } from './suite.js';
|
|
24
33
|
export { DEFAULT_ARGON2_COST, getSuite, isSupportedSuite, SUITE_KUBUN_CRED_1 } from './suite.js';
|
|
25
34
|
export { assertValidCredentialShape } from './validate.js';
|
|
26
35
|
export { CREDENTIAL_ADMINISTER_ACTION, credentialUserResource } from './vocabulary.js';
|
package/lib/index.js
CHANGED
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
export { createCredentialAPI } from './api.js';
|
|
2
2
|
export { createDeviceAuthority } from './authority.js';
|
|
3
|
+
export { compareBranchIDs, selectWinningBranch } from './branch.js';
|
|
3
4
|
export { buildCredentialEntry, buildCredentialKey } from './builder.js';
|
|
5
|
+
export { isCanonicalCompactToken } from './canonical-token.js';
|
|
4
6
|
export { credentialEntriesDigest, credentialEntryDigest, credentialKeyDigest, credentialWrappingDigest, digestParts } from './digest.js';
|
|
5
|
-
export { AmbiguousCredentialWrapping, CredentialAliasAlreadyBound, CredentialBindNotAuthorized, CredentialEntryAlreadyAliased, CredentialError, CredentialInvalidEntry, CredentialKeyLocked, CredentialKeyVersionMismatch, CredentialNotAuthorized, CredentialNotFound, CredentialPinRejected, CredentialSecretRejected, CredentialSupersededBranch, CredentialTampered, CredentialUnopenable, InvalidCredentialFactors, NoAvailableFactor, UnsupportedCredentialSuite } from './errors.js';
|
|
7
|
+
export { AmbiguousCredentialWrapping, CredentialAliasAlreadyBound, CredentialBindNotAuthorized, CredentialEntryAlreadyAliased, CredentialEntryConflict, CredentialEntryOwnerMismatch, CredentialError, CredentialGrantAccountMismatch, CredentialGrantCopyUnavailable, CredentialGrantOwnerAmbiguous, CredentialGrantRehomeNotAuthorized, CredentialInvalidEntry, CredentialKeyLocked, CredentialKeyVersionMismatch, CredentialManagerStoreMismatch, CredentialNotAuthorized, CredentialNotFound, CredentialPinRejected, CredentialRecipientRevoked, CredentialRepairPending, CredentialSecretRejected, CredentialSupersededBranch, CredentialTampered, CredentialUnopenable, InvalidCredentialFactors, NoAvailableFactor, UnsupportedCredentialSuite } from './errors.js';
|
|
8
|
+
export { repairCredentialFork } from './fork-repair.js';
|
|
9
|
+
export { signGrantorAuthority, verifyGrantorAuthority } from './grantor-authority.js';
|
|
6
10
|
export { createCredentialManager } from './manager.js';
|
|
7
|
-
export {
|
|
11
|
+
export { signCredentialManifest, verifyCredentialManifest } from './manifest-token.js';
|
|
8
12
|
export { createPinThrottle, DEFAULT_BASE_DELAY, DEFAULT_FREE_ATTEMPTS, DEFAULT_MAX_DELAY } from './pin-throttle.js';
|
|
9
13
|
export { entryProvenanceFields } from './provenance.js';
|
|
10
14
|
export { DBCredentialProvider, deserializeCredential, serializeCredential } from './provider.js';
|
|
15
|
+
export { CredentialRecordHookError, credentialGrantEntryID, credentialHolderTypes, credentialKeyVersionReader, credentialProofAuthorizer, credentialRecordTypes, credentialStoreForHooks } from './record-types.js';
|
|
11
16
|
export { RefreshingCredentialProvider } from './refreshing-credential-provider.js';
|
|
12
17
|
export { DEFAULT_ARGON2_COST, getSuite, isSupportedSuite, SUITE_KUBUN_CRED_1 } from './suite.js';
|
|
13
18
|
export { assertValidCredentialShape } from './validate.js';
|
package/lib/key-cache.d.ts
CHANGED
|
@@ -19,8 +19,8 @@ export type KeyCacheParams = {
|
|
|
19
19
|
*/
|
|
20
20
|
export type KeyCache = {
|
|
21
21
|
/**
|
|
22
|
-
* The cached key and the branch it resolved to, or null if absent
|
|
23
|
-
*
|
|
22
|
+
* The cached key and the branch it resolved to, or null if absent or expired.
|
|
23
|
+
* A cache hit still surfaces the branch so a
|
|
24
24
|
* post-fork write joins the same branch the first open resolved.
|
|
25
25
|
*/
|
|
26
26
|
get(keyID: string, keyVersion: number): {
|
|
@@ -28,12 +28,13 @@ export type KeyCache = {
|
|
|
28
28
|
branchID: string;
|
|
29
29
|
} | null;
|
|
30
30
|
/**
|
|
31
|
-
* Whether an unexpired
|
|
31
|
+
* Whether an unexpired key at this version is cached, without materialising a
|
|
32
32
|
* copy of it. Presence-only callers (`status`) use this so a probe never mints
|
|
33
33
|
* a secret buffer it immediately discards.
|
|
34
34
|
*/
|
|
35
35
|
has(keyID: string, keyVersion: number): boolean;
|
|
36
36
|
set(keyID: string, contentKey: Uint8Array, keyVersion: number, branchID: string): void;
|
|
37
|
+
evictVersion(keyID: string, keyVersion: number): void;
|
|
37
38
|
evict(keyID: string): void;
|
|
38
39
|
evictAll(): void;
|
|
39
40
|
};
|
package/lib/key-cache.js
CHANGED
|
@@ -11,25 +11,30 @@ export function createKeyCache(params = {}) {
|
|
|
11
11
|
* JavaScript cannot promise no interpreter-level copy survives, so this is
|
|
12
12
|
* best-effort hygiene, not a guarantee that no copy of the key remains.
|
|
13
13
|
*/ function evict(keyID) {
|
|
14
|
-
const
|
|
15
|
-
if (
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
14
|
+
const versions = entries.get(keyID);
|
|
15
|
+
if (versions == null) return;
|
|
16
|
+
for (const cached of versions.values())cached.contentKey.fill(0);
|
|
17
|
+
entries.delete(keyID);
|
|
18
|
+
}
|
|
19
|
+
function evictVersion(keyID, keyVersion) {
|
|
20
|
+
const versions = entries.get(keyID);
|
|
21
|
+
const cached = versions?.get(keyVersion);
|
|
22
|
+
if (cached == null) return;
|
|
23
|
+
cached.contentKey.fill(0);
|
|
24
|
+
versions?.delete(keyVersion);
|
|
25
|
+
if (versions?.size === 0) entries.delete(keyID);
|
|
19
26
|
}
|
|
20
27
|
/**
|
|
21
|
-
* The still-valid cached entry for this key/version, or null — evicting
|
|
22
|
-
*
|
|
23
|
-
* does: serving the pre-rotation key would decrypt entries the rotation was
|
|
24
|
-
* retiring. Shared by `get` and `has` so both apply identical validity and
|
|
28
|
+
* The still-valid cached entry for this key/version, or null — evicting an
|
|
29
|
+
* expired one on the way. Shared by `get` and `has` so both apply identical validity and
|
|
25
30
|
* lazy-eviction rules; neither exposes the buffer.
|
|
26
31
|
*/ function live(keyID, keyVersion) {
|
|
27
|
-
const cached = entries.get(keyID);
|
|
32
|
+
const cached = entries.get(keyID)?.get(keyVersion);
|
|
28
33
|
if (cached == null) {
|
|
29
34
|
return null;
|
|
30
35
|
}
|
|
31
|
-
if (now() - cached.unlockedAt >= ttl
|
|
32
|
-
|
|
36
|
+
if (now() - cached.unlockedAt >= ttl) {
|
|
37
|
+
evictVersion(keyID, keyVersion);
|
|
33
38
|
return null;
|
|
34
39
|
}
|
|
35
40
|
return cached;
|
|
@@ -53,18 +58,20 @@ export function createKeyCache(params = {}) {
|
|
|
53
58
|
return live(keyID, keyVersion) != null;
|
|
54
59
|
},
|
|
55
60
|
set (keyID, contentKey, keyVersion, branchID) {
|
|
56
|
-
|
|
61
|
+
evictVersion(keyID, keyVersion);
|
|
57
62
|
// Store a private clone so the caller's buffer is never the cached buffer:
|
|
58
63
|
// the caller may zero its own copy after use without disturbing the cache,
|
|
59
64
|
// and the cache may zero its clone on evict without disturbing the caller.
|
|
60
|
-
entries.
|
|
65
|
+
const versions = entries.get(keyID) ?? new Map();
|
|
66
|
+
versions.set(keyVersion, {
|
|
61
67
|
contentKey: contentKey.slice(),
|
|
62
|
-
keyVersion,
|
|
63
68
|
branchID,
|
|
64
69
|
unlockedAt: now()
|
|
65
70
|
});
|
|
71
|
+
entries.set(keyID, versions);
|
|
66
72
|
},
|
|
67
73
|
evict,
|
|
74
|
+
evictVersion,
|
|
68
75
|
evictAll () {
|
|
69
76
|
for (const keyID of [
|
|
70
77
|
...entries.keys()
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { type KeyAgreementIdentity, type SigningIdentity } from '@kokuin/token';
|
|
2
|
+
import { HLC } from '@kubun/hlc';
|
|
3
|
+
import type { CredentialFactorDescriptor, CredentialStoreAPI } from '@kubun/store-credential';
|
|
4
|
+
import type { Runtime } from '@sozai/runtime';
|
|
5
|
+
import { type CredentialAuthority } from './authority.js';
|
|
6
|
+
import { type KeyCache } from './key-cache.js';
|
|
7
|
+
import type { OpenFunctions } from './manager-open.js';
|
|
8
|
+
import type { RecordFunctions } from './manager-records.js';
|
|
9
|
+
import type { RemovalRecoveryFunctions } from './manager-removal-recovery.js';
|
|
10
|
+
import type { RotateFunctions } from './manager-rotate.js';
|
|
11
|
+
import type { CreateCredentialManagerParams, CredentialManager } from './manager-types.js';
|
|
12
|
+
import { type PinThrottle } from './pin-throttle.js';
|
|
13
|
+
import type { SecretFactorKind } from './suite.js';
|
|
14
|
+
/** Kinds a user can be prompted for, as opposed to ones held as key material. */
|
|
15
|
+
export declare const SECRET_FACTOR_KINDS: ReadonlyArray<SecretFactorKind>;
|
|
16
|
+
export declare function wrappingNamesRemovedDevice(wrapping: {
|
|
17
|
+
recipient_did: string | null;
|
|
18
|
+
factors: ReadonlyArray<CredentialFactorDescriptor>;
|
|
19
|
+
}, removed: ReadonlySet<string>): boolean;
|
|
20
|
+
export declare function isSecretFactorKind(kind: string): kind is SecretFactorKind;
|
|
21
|
+
export type ManagerState = {
|
|
22
|
+
inFlight: Map<string, Promise<void>>;
|
|
23
|
+
keyGeneration: Map<string, number>;
|
|
24
|
+
lockAllEpoch: number;
|
|
25
|
+
floorPromise?: Promise<void>;
|
|
26
|
+
completedRecoveryHead?: string;
|
|
27
|
+
};
|
|
28
|
+
export type ManagerContext = {
|
|
29
|
+
params: CreateCredentialManagerParams;
|
|
30
|
+
store: CredentialStoreAPI;
|
|
31
|
+
identity: SigningIdentity;
|
|
32
|
+
runtime: Runtime;
|
|
33
|
+
hlc: HLC;
|
|
34
|
+
authority: CredentialAuthority;
|
|
35
|
+
decrypting: KeyAgreementIdentity | null;
|
|
36
|
+
cache: KeyCache;
|
|
37
|
+
throttle: PinThrottle;
|
|
38
|
+
state: ManagerState;
|
|
39
|
+
ensureFloored: () => Promise<void>;
|
|
40
|
+
nextHLC: () => Promise<string>;
|
|
41
|
+
};
|
|
42
|
+
export type ManagerInternals = {
|
|
43
|
+
readonly records: RecordFunctions;
|
|
44
|
+
readonly open: OpenFunctions;
|
|
45
|
+
readonly rotate: RotateFunctions;
|
|
46
|
+
readonly removalRecovery: RemovalRecoveryFunctions;
|
|
47
|
+
readonly manager: CredentialManager;
|
|
48
|
+
createManager: (params: CreateCredentialManagerParams) => CredentialManager;
|
|
49
|
+
};
|
|
50
|
+
export declare function createManagerContext(params: CreateCredentialManagerParams): ManagerContext;
|