@interop/wallet-core 0.17.1 → 0.19.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/README.md +13 -13
- package/dist/clients/index.d.ts +4 -3
- package/dist/clients/index.d.ts.map +1 -1
- package/dist/clients/index.js +3 -2
- package/dist/clients/index.js.map +1 -1
- package/dist/clients/policy.d.ts +3 -3
- package/dist/clients/policy.d.ts.map +1 -1
- package/dist/clients/policy.js +1 -1
- package/dist/clients/revocation.d.ts +15 -15
- package/dist/clients/revocation.d.ts.map +1 -1
- package/dist/clients/revocation.js +19 -19
- package/dist/clients/revocation.js.map +1 -1
- package/dist/clients/rosterPolicy.d.ts +26 -25
- package/dist/clients/rosterPolicy.d.ts.map +1 -1
- package/dist/clients/rosterPolicy.js +29 -29
- package/dist/clients/rosterPolicy.js.map +1 -1
- package/dist/enrollment/enrollment.d.ts +16 -16
- package/dist/enrollment/enrollment.d.ts.map +1 -1
- package/dist/enrollment/enrollment.js +19 -19
- package/dist/enrollment/enrollment.js.map +1 -1
- package/dist/enrollment/index.d.ts +1 -1
- package/dist/enrollment/index.js +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/keys/clientKeyRecord.d.ts +16 -16
- package/dist/keys/clientKeyRecord.d.ts.map +1 -1
- package/dist/keys/clientKeyRecord.js +39 -35
- package/dist/keys/clientKeyRecord.js.map +1 -1
- package/dist/keys/index.d.ts +19 -17
- package/dist/keys/index.d.ts.map +1 -1
- package/dist/keys/index.js +16 -14
- package/dist/keys/index.js.map +1 -1
- package/dist/keys/rosterStore.d.ts +3 -3
- package/dist/keys/rosterStore.d.ts.map +1 -1
- package/dist/keys/rosterStore.js +13 -13
- package/dist/keys/rosterStore.js.map +1 -1
- package/dist/keys/userKey.d.ts +43 -0
- package/dist/keys/userKey.d.ts.map +1 -0
- package/dist/keys/{puk.js → userKey.js} +23 -21
- package/dist/keys/userKey.js.map +1 -0
- package/dist/keys/{pukCascade.d.ts → userKeyCascade.d.ts} +69 -69
- package/dist/keys/userKeyCascade.d.ts.map +1 -0
- package/dist/keys/{pukCascade.js → userKeyCascade.js} +67 -64
- package/dist/keys/userKeyCascade.js.map +1 -0
- package/dist/keys/{pukRoster.d.ts → userKeyRoster.d.ts} +73 -74
- package/dist/keys/userKeyRoster.d.ts.map +1 -0
- package/dist/keys/{pukRoster.js → userKeyRoster.js} +86 -83
- package/dist/keys/userKeyRoster.js.map +1 -0
- package/dist/recovery/index.d.ts +1 -1
- package/dist/recovery/index.js +1 -1
- package/dist/recovery/recoveryCode.d.ts +1 -1
- package/dist/recovery/recoveryCode.js +6 -6
- package/dist/recovery/recoveryRecord.d.ts +1 -1
- package/dist/recovery/recoveryWebvh.d.ts +1 -1
- package/dist/recovery/recoveryWebvh.js +1 -1
- package/dist/space/activity.d.ts +17 -1
- package/dist/space/activity.d.ts.map +1 -1
- package/dist/space/activity.js +22 -1
- package/dist/space/activity.js.map +1 -1
- package/dist/space/collections.d.ts +17 -17
- package/dist/space/collections.d.ts.map +1 -1
- package/dist/space/collections.js +16 -16
- package/dist/space/collections.js.map +1 -1
- package/dist/space/index.d.ts +3 -3
- package/dist/space/index.d.ts.map +1 -1
- package/dist/space/index.js +3 -3
- package/dist/space/index.js.map +1 -1
- package/dist/webvh/didWebvh.d.ts +1 -1
- package/dist/webvh/didWebvh.js +6 -6
- package/package.json +1 -1
- package/dist/keys/puk.d.ts +0 -42
- package/dist/keys/puk.d.ts.map +0 -1
- package/dist/keys/puk.js.map +0 -1
- package/dist/keys/pukCascade.d.ts.map +0 -1
- package/dist/keys/pukCascade.js.map +0 -1
- package/dist/keys/pukRoster.d.ts.map +0 -1
- package/dist/keys/pukRoster.js.map +0 -1
|
@@ -2,92 +2,92 @@
|
|
|
2
2
|
* Copyright (c) 2026 Interop Alliance. All rights reserved.
|
|
3
3
|
*/
|
|
4
4
|
/**
|
|
5
|
-
* The per-collection half of the
|
|
6
|
-
* current epoch has moved to a fresh
|
|
7
|
-
* spent or revoked), every encrypted collection must take a new epoch
|
|
8
|
-
* the fresh
|
|
9
|
-
* an epoch key the revoked party still holds.
|
|
5
|
+
* The per-collection half of the user key rotation cascade: once the roster's
|
|
6
|
+
* current epoch has moved to a fresh user key (a client revoked, a recovery
|
|
7
|
+
* code spent or revoked), every encrypted collection must take a new epoch
|
|
8
|
+
* naming the fresh user key and retire the old one -- otherwise writes keep
|
|
9
|
+
* landing under an epoch key the revoked party still holds.
|
|
10
10
|
*
|
|
11
11
|
* The staleness rule is the one the revocation spike verified: **a collection
|
|
12
|
-
* is stale exactly when its current epoch names a
|
|
12
|
+
* is stale exactly when its current epoch names a user key KAK other than the
|
|
13
13
|
* roster's current** -- detectable from durable data alone, no checkpoint
|
|
14
|
-
* resource anywhere, which is what makes a crashed cascade resumable by a
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
14
|
+
* resource anywhere, which is what makes a crashed cascade resumable by a naive
|
|
15
|
+
* full re-run (and what the login-time completion sweep re-checks). Which KAKs
|
|
16
|
+
* are "user key generations" is read from the roster itself: its epochs ARE the
|
|
17
|
+
* user key generations, each escrow-wrapped to every enrolled client, so any
|
|
18
|
+
* enrolled client can recover any generation's key ({@link
|
|
19
|
+
* unwrapUserKeyGenerations}) -- needed both to recognize a stale epoch and to
|
|
20
|
+
* escrow the fresh user key into a stranded collection's history.
|
|
21
21
|
*
|
|
22
22
|
* One residue, accepted: a collection with NO epochs gets the newest prior
|
|
23
23
|
* generation installed as its first epoch (pre-epoch envelopes are sealed to
|
|
24
|
-
* the era's
|
|
25
|
-
* epoch-of-that-generation envelopes) -- envelopes older than that
|
|
26
|
-
* generation, on a collection every prior cascade missed, stay readable only
|
|
27
|
-
*
|
|
24
|
+
* the era's user key KAK, and the user key IS the epoch construction, so they
|
|
25
|
+
* ARE epoch-of-that-generation envelopes) -- envelopes older than that
|
|
26
|
+
* generation, on a collection every prior cascade missed, stay readable only to
|
|
27
|
+
* sessions that held the older key.
|
|
28
28
|
*/
|
|
29
29
|
import type { IKeyAgreementKey } from '@interop/data-integrity-core';
|
|
30
30
|
import type { CollectionEncryption } from '@interop/was-client';
|
|
31
31
|
import { type EncryptionDescriptorStore, type RecipientPublicKey } from '@interop/was-client/edv';
|
|
32
|
-
import { type
|
|
32
|
+
import { type UserKey } from './userKey.js';
|
|
33
33
|
/**
|
|
34
|
-
* A
|
|
34
|
+
* A user key presented as an epoch-roster recipient: the kid is the
|
|
35
35
|
* self-describing `<did:key>#<fingerprint>` form every collection epoch names
|
|
36
36
|
* the user under, and the public key is the did:key's own multibase.
|
|
37
37
|
*
|
|
38
38
|
* @param options {object}
|
|
39
|
-
* @param options.
|
|
39
|
+
* @param options.userKey {UserKey}
|
|
40
40
|
* @returns {RecipientPublicKey}
|
|
41
41
|
*/
|
|
42
|
-
export declare function
|
|
43
|
-
|
|
42
|
+
export declare function userKeyAsRecipient({ userKey }: {
|
|
43
|
+
userKey: UserKey;
|
|
44
44
|
}): RecipientPublicKey;
|
|
45
45
|
/**
|
|
46
|
-
* Recovers every
|
|
47
|
-
* is one generation (its id the generation's did:key, its wrapped secret
|
|
48
|
-
* generation's raw key), escrow-wrapped to every enrolled client -- so this
|
|
49
|
-
* client's key-agreement key unwraps them all, in roster (chronological)
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
46
|
+
* Recovers every user key generation from the roster descriptor: each roster
|
|
47
|
+
* epoch is one generation (its id the generation's did:key, its wrapped secret
|
|
48
|
+
* the generation's raw key), escrow-wrapped to every enrolled client -- so this
|
|
49
|
+
* client's key-agreement key unwraps them all, in roster (chronological) order.
|
|
50
|
+
* A generation whose wrap is missing or fails to unwrap is skipped rather than
|
|
51
|
+
* fatal (the cascade then simply cannot recognize or escrow that generation;
|
|
52
|
+
* the current epoch always unwraps or the roster read itself would have
|
|
53
|
+
* refused).
|
|
54
54
|
*
|
|
55
55
|
* @param options {object}
|
|
56
56
|
* @param options.descriptor {CollectionEncryption} the roster descriptor
|
|
57
57
|
* @param options.clientKeyAgreementKey {IKeyAgreementKey} this client's own
|
|
58
58
|
* (identity) key-agreement key
|
|
59
|
-
* @returns {Promise<
|
|
59
|
+
* @returns {Promise<UserKey[]>} the generations, oldest first
|
|
60
60
|
*/
|
|
61
|
-
export declare function
|
|
61
|
+
export declare function unwrapUserKeyGenerations({ descriptor, clientKeyAgreementKey }: {
|
|
62
62
|
descriptor: CollectionEncryption;
|
|
63
63
|
clientKeyAgreementKey: IKeyAgreementKey;
|
|
64
|
-
}): Promise<
|
|
64
|
+
}): Promise<UserKey[]>;
|
|
65
65
|
/**
|
|
66
|
-
* What one collection's cascade step did: `noop` (already on the current
|
|
67
|
-
*
|
|
68
|
-
* gained its first epoch; nothing to retire), `escrowed` (the current
|
|
69
|
-
* wrap was completed into history with no stale epoch to rotate), or
|
|
70
|
-
* `rotated` (a fresh epoch sealed to the current
|
|
71
|
-
* retired -- the pre-epoch install falls through to this).
|
|
66
|
+
* What one collection's cascade step did: `noop` (already on the current user
|
|
67
|
+
* key), `installed` (a pre-epoch collection on a first-generation account
|
|
68
|
+
* gained its first epoch; nothing to retire), `escrowed` (the current user
|
|
69
|
+
* key's wrap was completed into history with no stale epoch to rotate), or
|
|
70
|
+
* `rotated` (a fresh epoch sealed to the current user key, the stale
|
|
71
|
+
* generations retired -- the pre-epoch install falls through to this).
|
|
72
72
|
*/
|
|
73
|
-
export type
|
|
73
|
+
export type CollectionUserKeyRotationOutcome = 'noop' | 'installed' | 'escrowed' | 'rotated';
|
|
74
74
|
/**
|
|
75
|
-
* Brings ONE encrypted collection's epoch roster onto the current
|
|
76
|
-
* per-collection op of the revocation cascade and of the completion sweep:
|
|
75
|
+
* Brings ONE encrypted collection's epoch roster onto the current user key --
|
|
76
|
+
* the per-collection op of the revocation cascade and of the completion sweep:
|
|
77
77
|
*
|
|
78
78
|
* - **No epochs yet**: install the newest PRIOR generation as the first epoch
|
|
79
|
-
* (see the module doc), wrapped to that generation and the current
|
|
79
|
+
* (see the module doc), wrapped to that generation and the current user key,
|
|
80
80
|
* then fall through to the rotation. A first-generation account (nothing
|
|
81
|
-
* prior) installs the current
|
|
81
|
+
* prior) installs the current user key alone and is done.
|
|
82
82
|
* - **Stale current epoch** (names a non-current generation): one
|
|
83
|
-
* `replaceRecipient` write -- the current
|
|
83
|
+
* `replaceRecipient` write -- the current user key escrowed into every epoch,
|
|
84
84
|
* a fresh epoch minted without the stale generations. Two requests per
|
|
85
85
|
* collection; app recipients and other readers ride through untouched (the
|
|
86
86
|
* default did:key resolver re-wraps them).
|
|
87
87
|
* - **Current already** and fully escrowed: no write at all, so a naive
|
|
88
88
|
* re-run after a mid-cascade crash converges with zero redundant epochs.
|
|
89
89
|
*
|
|
90
|
-
* The pull axis is deliberately a no-op here: a
|
|
90
|
+
* The pull axis is deliberately a no-op here: a user key rotation follows a
|
|
91
91
|
* document edit (client revocation, code retirement) that already killed the
|
|
92
92
|
* revoked party's server-side access everywhere under the current-key-set
|
|
93
93
|
* rule -- there is no per-collection revoke.
|
|
@@ -95,35 +95,35 @@ export type CollectionPukRotationOutcome = 'noop' | 'installed' | 'escrowed' | '
|
|
|
95
95
|
* @param options {object}
|
|
96
96
|
* @param options.store {EncryptionDescriptorStore} the collection's
|
|
97
97
|
* descriptor store
|
|
98
|
-
* @param options.
|
|
99
|
-
* @param options.generations {
|
|
100
|
-
* could unwrap ({@link
|
|
101
|
-
* @returns {Promise<
|
|
98
|
+
* @param options.userKey {UserKey} the roster's CURRENT user key
|
|
99
|
+
* @param options.generations {UserKey[]} every roster generation this client
|
|
100
|
+
* could unwrap ({@link unwrapUserKeyGenerations}), oldest first
|
|
101
|
+
* @returns {Promise<CollectionUserKeyRotationOutcome>}
|
|
102
102
|
*/
|
|
103
|
-
export declare function
|
|
103
|
+
export declare function rotateCollectionEpochsToUserKey({ store, userKey, generations }: {
|
|
104
104
|
store: EncryptionDescriptorStore;
|
|
105
|
-
|
|
106
|
-
generations:
|
|
107
|
-
}): Promise<
|
|
105
|
+
userKey: UserKey;
|
|
106
|
+
generations: UserKey[];
|
|
107
|
+
}): Promise<CollectionUserKeyRotationOutcome>;
|
|
108
108
|
/**
|
|
109
109
|
* What the collection fan-out did, per collection id: the outcomes for every
|
|
110
110
|
* collection that needed (or took) work, and the per-collection failures the
|
|
111
111
|
* caller surfaces -- the cascade never aborts on one stuck collection.
|
|
112
112
|
*/
|
|
113
|
-
export interface
|
|
114
|
-
outcomes: Record<string,
|
|
113
|
+
export interface UserKeyCascadeResult {
|
|
114
|
+
outcomes: Record<string, CollectionUserKeyRotationOutcome>;
|
|
115
115
|
failed: Array<{
|
|
116
116
|
collectionId: string;
|
|
117
117
|
error: unknown;
|
|
118
118
|
}>;
|
|
119
119
|
}
|
|
120
120
|
/**
|
|
121
|
-
* The collection fan-out of the
|
|
122
|
-
* collection onto the roster's current
|
|
123
|
-
* generations from the roster once. The wallet supplies what only
|
|
124
|
-
* which collections exist (`collectionIds`) and how to reach each
|
|
125
|
-
* descriptor (`storeFor`); the per-collection staleness rule and re-epoch
|
|
126
|
-
* in {@link
|
|
121
|
+
* The collection fan-out of the user key rotation cascade: re-epochs every
|
|
122
|
+
* named collection onto the roster's current user key, in parallel, unwrapping
|
|
123
|
+
* the user key generations from the roster once. The wallet supplies what only
|
|
124
|
+
* it knows -- which collections exist (`collectionIds`) and how to reach each
|
|
125
|
+
* one's descriptor (`storeFor`); the per-collection staleness rule and re-epoch
|
|
126
|
+
* live in {@link rotateCollectionEpochsToUserKey}.
|
|
127
127
|
*
|
|
128
128
|
* A collection that fails is reported in `failed` and the rest proceed; the
|
|
129
129
|
* caller decides what a failure means (the login-time completion sweep is the
|
|
@@ -140,18 +140,18 @@ export interface PukCascadeResult {
|
|
|
140
140
|
* own task so a throwing check lands in that collection's `failed` entry
|
|
141
141
|
* (e.g. a standard collection an account never provisioned server-side)
|
|
142
142
|
* @param options.rosterDescriptor {CollectionEncryption} the freshly read
|
|
143
|
-
* `key-map/
|
|
143
|
+
* `key-map/user-key.json` roster (the source of the user key generations)
|
|
144
144
|
* @param options.clientKeyAgreementKey {IKeyAgreementKey} this client's own
|
|
145
145
|
* (identity) key-agreement key, unwrapping the generations
|
|
146
|
-
* @param options.
|
|
147
|
-
* @returns {Promise<
|
|
146
|
+
* @param options.userKey {UserKey} the roster's current user key
|
|
147
|
+
* @returns {Promise<UserKeyCascadeResult>}
|
|
148
148
|
*/
|
|
149
|
-
export declare function
|
|
149
|
+
export declare function cascadeCollectionsToUserKey({ collectionIds, storeFor, isEncrypted, rosterDescriptor, clientKeyAgreementKey, userKey }: {
|
|
150
150
|
collectionIds: string[];
|
|
151
151
|
storeFor: (collectionId: string) => EncryptionDescriptorStore;
|
|
152
152
|
isEncrypted?: (collectionId: string) => Promise<boolean>;
|
|
153
153
|
rosterDescriptor: CollectionEncryption;
|
|
154
154
|
clientKeyAgreementKey: IKeyAgreementKey;
|
|
155
|
-
|
|
156
|
-
}): Promise<
|
|
157
|
-
//# sourceMappingURL=
|
|
155
|
+
userKey: UserKey;
|
|
156
|
+
}): Promise<UserKeyCascadeResult>;
|
|
157
|
+
//# sourceMappingURL=userKeyCascade.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"userKeyCascade.d.ts","sourceRoot":"","sources":["../../src/keys/userKeyCascade.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAA;AACpE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAA;AAC/D,OAAO,EAML,KAAK,yBAAyB,EAC9B,KAAK,kBAAkB,EACxB,MAAM,yBAAyB,CAAA;AAEhC,OAAO,EAAoB,KAAK,OAAO,EAAE,MAAM,cAAc,CAAA;AAE7D;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAAC,EACjC,OAAO,EACR,EAAE;IACD,OAAO,EAAE,OAAO,CAAA;CACjB,GAAG,kBAAkB,CAKrB;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,wBAAwB,CAAC,EAC7C,UAAU,EACV,qBAAqB,EACtB,EAAE;IACD,UAAU,EAAE,oBAAoB,CAAA;IAChC,qBAAqB,EAAE,gBAAgB,CAAA;CACxC,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC,CAkBrB;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,gCAAgC,GAC1C,MAAM,GAAG,WAAW,GAAG,UAAU,GAAG,SAAS,CAAA;AAE/C;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAsB,+BAA+B,CAAC,EACpD,KAAK,EACL,OAAO,EACP,WAAW,EACZ,EAAE;IACD,KAAK,EAAE,yBAAyB,CAAA;IAChC,OAAO,EAAE,OAAO,CAAA;IAChB,WAAW,EAAE,OAAO,EAAE,CAAA;CACvB,GAAG,OAAO,CAAC,gCAAgC,CAAC,CA6H5C;AAED;;;;GAIG;AACH,MAAM,WAAW,oBAAoB;IACnC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,gCAAgC,CAAC,CAAA;IAC1D,MAAM,EAAE,KAAK,CAAC;QAAE,YAAY,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,OAAO,CAAA;KAAE,CAAC,CAAA;CACxD;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAsB,2BAA2B,CAAC,EAChD,aAAa,EACb,QAAQ,EACR,WAAW,EACX,gBAAgB,EAChB,qBAAqB,EACrB,OAAO,EACR,EAAE;IACD,aAAa,EAAE,MAAM,EAAE,CAAA;IACvB,QAAQ,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,yBAAyB,CAAA;IAC7D,WAAW,CAAC,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;IACxD,gBAAgB,EAAE,oBAAoB,CAAA;IACtC,qBAAqB,EAAE,gBAAgB,CAAA;IACvC,OAAO,EAAE,OAAO,CAAA;CACjB,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAwBhC"}
|
|
@@ -1,38 +1,38 @@
|
|
|
1
1
|
import { addRecipient, epochKeyIdFor, initRecipients, replaceRecipient, unwrapEpochSecret } from '@interop/was-client/edv';
|
|
2
2
|
import { ValidationError } from '@interop/was-client';
|
|
3
|
-
import {
|
|
3
|
+
import { userKeyVaultKeys } from './userKey.js';
|
|
4
4
|
/**
|
|
5
|
-
* A
|
|
5
|
+
* A user key presented as an epoch-roster recipient: the kid is the
|
|
6
6
|
* self-describing `<did:key>#<fingerprint>` form every collection epoch names
|
|
7
7
|
* the user under, and the public key is the did:key's own multibase.
|
|
8
8
|
*
|
|
9
9
|
* @param options {object}
|
|
10
|
-
* @param options.
|
|
10
|
+
* @param options.userKey {UserKey}
|
|
11
11
|
* @returns {RecipientPublicKey}
|
|
12
12
|
*/
|
|
13
|
-
export function
|
|
13
|
+
export function userKeyAsRecipient({ userKey }) {
|
|
14
14
|
return {
|
|
15
|
-
id: epochKeyIdFor(
|
|
16
|
-
publicKeyMultibase:
|
|
15
|
+
id: epochKeyIdFor(userKey.id),
|
|
16
|
+
publicKeyMultibase: userKey.id.split(':')[2]
|
|
17
17
|
};
|
|
18
18
|
}
|
|
19
19
|
/**
|
|
20
|
-
* Recovers every
|
|
21
|
-
* is one generation (its id the generation's did:key, its wrapped secret
|
|
22
|
-
* generation's raw key), escrow-wrapped to every enrolled client -- so this
|
|
23
|
-
* client's key-agreement key unwraps them all, in roster (chronological)
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
20
|
+
* Recovers every user key generation from the roster descriptor: each roster
|
|
21
|
+
* epoch is one generation (its id the generation's did:key, its wrapped secret
|
|
22
|
+
* the generation's raw key), escrow-wrapped to every enrolled client -- so this
|
|
23
|
+
* client's key-agreement key unwraps them all, in roster (chronological) order.
|
|
24
|
+
* A generation whose wrap is missing or fails to unwrap is skipped rather than
|
|
25
|
+
* fatal (the cascade then simply cannot recognize or escrow that generation;
|
|
26
|
+
* the current epoch always unwraps or the roster read itself would have
|
|
27
|
+
* refused).
|
|
28
28
|
*
|
|
29
29
|
* @param options {object}
|
|
30
30
|
* @param options.descriptor {CollectionEncryption} the roster descriptor
|
|
31
31
|
* @param options.clientKeyAgreementKey {IKeyAgreementKey} this client's own
|
|
32
32
|
* (identity) key-agreement key
|
|
33
|
-
* @returns {Promise<
|
|
33
|
+
* @returns {Promise<UserKey[]>} the generations, oldest first
|
|
34
34
|
*/
|
|
35
|
-
export async function
|
|
35
|
+
export async function unwrapUserKeyGenerations({ descriptor, clientKeyAgreementKey }) {
|
|
36
36
|
const generations = [];
|
|
37
37
|
for (const epoch of descriptor.epochs ?? []) {
|
|
38
38
|
const entry = epoch.recipients.find(recipient => recipient.header.kid === clientKeyAgreementKey.id);
|
|
@@ -50,22 +50,22 @@ export async function unwrapPukGenerations({ descriptor, clientKeyAgreementKey }
|
|
|
50
50
|
return generations;
|
|
51
51
|
}
|
|
52
52
|
/**
|
|
53
|
-
* Brings ONE encrypted collection's epoch roster onto the current
|
|
54
|
-
* per-collection op of the revocation cascade and of the completion sweep:
|
|
53
|
+
* Brings ONE encrypted collection's epoch roster onto the current user key --
|
|
54
|
+
* the per-collection op of the revocation cascade and of the completion sweep:
|
|
55
55
|
*
|
|
56
56
|
* - **No epochs yet**: install the newest PRIOR generation as the first epoch
|
|
57
|
-
* (see the module doc), wrapped to that generation and the current
|
|
57
|
+
* (see the module doc), wrapped to that generation and the current user key,
|
|
58
58
|
* then fall through to the rotation. A first-generation account (nothing
|
|
59
|
-
* prior) installs the current
|
|
59
|
+
* prior) installs the current user key alone and is done.
|
|
60
60
|
* - **Stale current epoch** (names a non-current generation): one
|
|
61
|
-
* `replaceRecipient` write -- the current
|
|
61
|
+
* `replaceRecipient` write -- the current user key escrowed into every epoch,
|
|
62
62
|
* a fresh epoch minted without the stale generations. Two requests per
|
|
63
63
|
* collection; app recipients and other readers ride through untouched (the
|
|
64
64
|
* default did:key resolver re-wraps them).
|
|
65
65
|
* - **Current already** and fully escrowed: no write at all, so a naive
|
|
66
66
|
* re-run after a mid-cascade crash converges with zero redundant epochs.
|
|
67
67
|
*
|
|
68
|
-
* The pull axis is deliberately a no-op here: a
|
|
68
|
+
* The pull axis is deliberately a no-op here: a user key rotation follows a
|
|
69
69
|
* document edit (client revocation, code retirement) that already killed the
|
|
70
70
|
* revoked party's server-side access everywhere under the current-key-set
|
|
71
71
|
* rule -- there is no per-collection revoke.
|
|
@@ -73,32 +73,35 @@ export async function unwrapPukGenerations({ descriptor, clientKeyAgreementKey }
|
|
|
73
73
|
* @param options {object}
|
|
74
74
|
* @param options.store {EncryptionDescriptorStore} the collection's
|
|
75
75
|
* descriptor store
|
|
76
|
-
* @param options.
|
|
77
|
-
* @param options.generations {
|
|
78
|
-
* could unwrap ({@link
|
|
79
|
-
* @returns {Promise<
|
|
76
|
+
* @param options.userKey {UserKey} the roster's CURRENT user key
|
|
77
|
+
* @param options.generations {UserKey[]} every roster generation this client
|
|
78
|
+
* could unwrap ({@link unwrapUserKeyGenerations}), oldest first
|
|
79
|
+
* @returns {Promise<CollectionUserKeyRotationOutcome>}
|
|
80
80
|
*/
|
|
81
|
-
export async function
|
|
81
|
+
export async function rotateCollectionEpochsToUserKey({ store, userKey, generations }) {
|
|
82
82
|
const current = await store.read();
|
|
83
83
|
if (current === null) {
|
|
84
84
|
return 'noop';
|
|
85
85
|
}
|
|
86
86
|
let descriptor = current.descriptor;
|
|
87
|
-
const staleGenerations = generations.filter(generation => generation.id !==
|
|
87
|
+
const staleGenerations = generations.filter(generation => generation.id !== userKey.id);
|
|
88
88
|
if (!descriptor.epochs?.length || !descriptor.currentEpoch) {
|
|
89
|
-
// Pre-epoch: the collection's envelopes are sealed to the era's
|
|
90
|
-
// which -- the
|
|
89
|
+
// Pre-epoch: the collection's envelopes are sealed to the era's user key KAK,
|
|
90
|
+
// which -- the user key being the epoch construction -- makes them epoch-of-
|
|
91
91
|
// that-generation envelopes. Install that generation as the first epoch.
|
|
92
92
|
const previous = staleGenerations[staleGenerations.length - 1];
|
|
93
93
|
try {
|
|
94
94
|
const installed = await initRecipients({
|
|
95
95
|
store,
|
|
96
96
|
recipients: previous
|
|
97
|
-
? [
|
|
98
|
-
|
|
97
|
+
? [
|
|
98
|
+
userKeyAsRecipient({ userKey: previous }),
|
|
99
|
+
userKeyAsRecipient({ userKey })
|
|
100
|
+
]
|
|
101
|
+
: [userKeyAsRecipient({ userKey })],
|
|
99
102
|
epoch: previous
|
|
100
103
|
? { epochId: previous.id, secret: previous.secret }
|
|
101
|
-
: { epochId:
|
|
104
|
+
: { epochId: userKey.id, secret: userKey.secret }
|
|
102
105
|
});
|
|
103
106
|
if (!previous) {
|
|
104
107
|
// A first-generation account: nothing prior to escrow, nothing to
|
|
@@ -126,65 +129,65 @@ export async function rotateCollectionEpochsToPuk({ store, puk, generations }) {
|
|
|
126
129
|
const staleKids = staleGenerations
|
|
127
130
|
.map(generation => epochKeyIdFor(generation.id))
|
|
128
131
|
.filter(kid => currentKids.has(kid));
|
|
129
|
-
const
|
|
132
|
+
const userKeyKid = epochKeyIdFor(userKey.id);
|
|
130
133
|
if (staleKids.length === 0) {
|
|
131
|
-
const escrowComplete = (descriptor.epochs ?? []).every(epoch => epoch.recipients.some(entry => entry.header.kid ===
|
|
132
|
-
if (currentKids.has(
|
|
134
|
+
const escrowComplete = (descriptor.epochs ?? []).every(epoch => epoch.recipients.some(entry => entry.header.kid === userKeyKid));
|
|
135
|
+
if (currentKids.has(userKeyKid) && escrowComplete) {
|
|
133
136
|
return 'noop';
|
|
134
137
|
}
|
|
135
138
|
}
|
|
136
139
|
if (staleKids.length > 0) {
|
|
137
140
|
// The escrow owner: the newest stale generation still named by the
|
|
138
141
|
// current epoch, which the cascade invariant escrowed into every prior
|
|
139
|
-
// epoch -- so it unwraps the whole history for the fresh
|
|
142
|
+
// epoch -- so it unwraps the whole history for the fresh user key's escrow.
|
|
140
143
|
const ownerGeneration = [...staleGenerations]
|
|
141
144
|
.reverse()
|
|
142
145
|
.find(generation => currentKids.has(epochKeyIdFor(generation.id)));
|
|
143
|
-
const owner =
|
|
146
|
+
const owner = userKeyVaultKeys({ userKey: ownerGeneration });
|
|
144
147
|
await replaceRecipient({
|
|
145
148
|
store,
|
|
146
149
|
retire: staleKids,
|
|
147
|
-
recipient:
|
|
150
|
+
recipient: userKeyAsRecipient({ userKey }),
|
|
148
151
|
owner: { keyAgreementKey: owner.keyAgreementKey },
|
|
149
152
|
pull: async () => { }
|
|
150
153
|
});
|
|
151
154
|
return 'rotated';
|
|
152
155
|
}
|
|
153
|
-
if (!currentKids.has(
|
|
154
|
-
// No stale generation to retire, but the current
|
|
155
|
-
// at all: this collection's roster is not
|
|
156
|
+
if (!currentKids.has(userKeyKid)) {
|
|
157
|
+
// No stale generation to retire, but the current user key is not a recipient
|
|
158
|
+
// at all: this collection's roster is not user-key-owned in a shape this
|
|
156
159
|
// cascade can heal -- surface it rather than silently minting an epoch
|
|
157
160
|
// whose history the account cannot read.
|
|
158
|
-
throw new Error('The collection current epoch names no
|
|
159
|
-
'can recognize; its roster cannot be rotated to the current
|
|
161
|
+
throw new Error('The collection current epoch names no user key generation this client ' +
|
|
162
|
+
'can recognize; its roster cannot be rotated to the current user key.');
|
|
160
163
|
}
|
|
161
|
-
// Escrow completion only: the current epoch is already on the current
|
|
164
|
+
// Escrow completion only: the current epoch is already on the current user key,
|
|
162
165
|
// but some historical epoch is missing its wrap. The owner must unwrap
|
|
163
166
|
// exactly those epochs -- the newest generation named by every incomplete
|
|
164
167
|
// epoch.
|
|
165
|
-
const incomplete = (descriptor.epochs ?? []).filter(epoch => !epoch.recipients.some(entry => entry.header.kid ===
|
|
168
|
+
const incomplete = (descriptor.epochs ?? []).filter(epoch => !epoch.recipients.some(entry => entry.header.kid === userKeyKid));
|
|
166
169
|
const escrowOwner = [...generations]
|
|
167
170
|
.reverse()
|
|
168
171
|
.find(generation => incomplete.every(epoch => epoch.recipients.some(entry => entry.header.kid === epochKeyIdFor(generation.id))));
|
|
169
172
|
if (!escrowOwner) {
|
|
170
|
-
throw new Error("The current
|
|
171
|
-
'no held
|
|
173
|
+
throw new Error("The current user key cannot be escrowed into this collection's history: " +
|
|
174
|
+
'no held user key generation is a recipient of every incomplete epoch.');
|
|
172
175
|
}
|
|
173
|
-
const owner =
|
|
176
|
+
const owner = userKeyVaultKeys({ userKey: escrowOwner });
|
|
174
177
|
await addRecipient({
|
|
175
178
|
store,
|
|
176
|
-
recipient:
|
|
179
|
+
recipient: userKeyAsRecipient({ userKey }),
|
|
177
180
|
owner: { keyAgreementKey: owner.keyAgreementKey }
|
|
178
181
|
});
|
|
179
182
|
return 'escrowed';
|
|
180
183
|
}
|
|
181
184
|
/**
|
|
182
|
-
* The collection fan-out of the
|
|
183
|
-
* collection onto the roster's current
|
|
184
|
-
* generations from the roster once. The wallet supplies what only
|
|
185
|
-
* which collections exist (`collectionIds`) and how to reach each
|
|
186
|
-
* descriptor (`storeFor`); the per-collection staleness rule and re-epoch
|
|
187
|
-
* in {@link
|
|
185
|
+
* The collection fan-out of the user key rotation cascade: re-epochs every
|
|
186
|
+
* named collection onto the roster's current user key, in parallel, unwrapping
|
|
187
|
+
* the user key generations from the roster once. The wallet supplies what only
|
|
188
|
+
* it knows -- which collections exist (`collectionIds`) and how to reach each
|
|
189
|
+
* one's descriptor (`storeFor`); the per-collection staleness rule and re-epoch
|
|
190
|
+
* live in {@link rotateCollectionEpochsToUserKey}.
|
|
188
191
|
*
|
|
189
192
|
* A collection that fails is reported in `failed` and the rest proceed; the
|
|
190
193
|
* caller decides what a failure means (the login-time completion sweep is the
|
|
@@ -201,14 +204,14 @@ export async function rotateCollectionEpochsToPuk({ store, puk, generations }) {
|
|
|
201
204
|
* own task so a throwing check lands in that collection's `failed` entry
|
|
202
205
|
* (e.g. a standard collection an account never provisioned server-side)
|
|
203
206
|
* @param options.rosterDescriptor {CollectionEncryption} the freshly read
|
|
204
|
-
* `key-map/
|
|
207
|
+
* `key-map/user-key.json` roster (the source of the user key generations)
|
|
205
208
|
* @param options.clientKeyAgreementKey {IKeyAgreementKey} this client's own
|
|
206
209
|
* (identity) key-agreement key, unwrapping the generations
|
|
207
|
-
* @param options.
|
|
208
|
-
* @returns {Promise<
|
|
210
|
+
* @param options.userKey {UserKey} the roster's current user key
|
|
211
|
+
* @returns {Promise<UserKeyCascadeResult>}
|
|
209
212
|
*/
|
|
210
|
-
export async function
|
|
211
|
-
const generations = await
|
|
213
|
+
export async function cascadeCollectionsToUserKey({ collectionIds, storeFor, isEncrypted, rosterDescriptor, clientKeyAgreementKey, userKey }) {
|
|
214
|
+
const generations = await unwrapUserKeyGenerations({
|
|
212
215
|
descriptor: rosterDescriptor,
|
|
213
216
|
clientKeyAgreementKey
|
|
214
217
|
});
|
|
@@ -219,9 +222,9 @@ export async function cascadeCollectionsToPuk({ collectionIds, storeFor, isEncry
|
|
|
219
222
|
if (isEncrypted && !(await isEncrypted(collectionId))) {
|
|
220
223
|
return;
|
|
221
224
|
}
|
|
222
|
-
outcomes[collectionId] = await
|
|
225
|
+
outcomes[collectionId] = await rotateCollectionEpochsToUserKey({
|
|
223
226
|
store: storeFor(collectionId),
|
|
224
|
-
|
|
227
|
+
userKey,
|
|
225
228
|
generations
|
|
226
229
|
});
|
|
227
230
|
}
|
|
@@ -231,4 +234,4 @@ export async function cascadeCollectionsToPuk({ collectionIds, storeFor, isEncry
|
|
|
231
234
|
}));
|
|
232
235
|
return { outcomes, failed };
|
|
233
236
|
}
|
|
234
|
-
//# sourceMappingURL=
|
|
237
|
+
//# sourceMappingURL=userKeyCascade.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"userKeyCascade.js","sourceRoot":"","sources":["../../src/keys/userKeyCascade.ts"],"names":[],"mappings":"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"}
|