@interop/wallet-core 0.21.0 → 0.22.1
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 +5 -2
- package/dist/descriptors/acquire.d.ts +23 -15
- package/dist/descriptors/acquire.d.ts.map +1 -1
- package/dist/descriptors/acquire.js +11 -8
- package/dist/descriptors/acquire.js.map +1 -1
- package/dist/descriptors/cipher.d.ts +9 -3
- package/dist/descriptors/cipher.d.ts.map +1 -1
- package/dist/descriptors/cipher.js +43 -12
- package/dist/descriptors/cipher.js.map +1 -1
- package/dist/descriptors/index.d.ts +6 -2
- package/dist/descriptors/index.d.ts.map +1 -1
- package/dist/descriptors/index.js +6 -2
- package/dist/descriptors/index.js.map +1 -1
- package/dist/keyring/index.d.ts +8 -3
- package/dist/keyring/index.d.ts.map +1 -1
- package/dist/keyring/index.js +8 -3
- package/dist/keyring/index.js.map +1 -1
- package/dist/keyring/record.d.ts +89 -18
- package/dist/keyring/record.d.ts.map +1 -1
- package/dist/keyring/record.js +119 -23
- package/dist/keyring/record.js.map +1 -1
- package/dist/keys/index.d.ts +5 -0
- package/dist/keys/index.d.ts.map +1 -1
- package/dist/keys/index.js +4 -0
- package/dist/keys/index.js.map +1 -1
- package/dist/keys/spaceEpochs.d.ts +95 -0
- package/dist/keys/spaceEpochs.d.ts.map +1 -0
- package/dist/keys/spaceEpochs.js +71 -0
- package/dist/keys/spaceEpochs.js.map +1 -0
- package/dist/keys/userKeyCascade.d.ts +14 -16
- package/dist/keys/userKeyCascade.d.ts.map +1 -1
- package/dist/keys/userKeyCascade.js +9 -43
- package/dist/keys/userKeyCascade.js.map +1 -1
- package/dist/recovery/recoveryRecord.d.ts +18 -13
- package/dist/recovery/recoveryRecord.d.ts.map +1 -1
- package/dist/recovery/recoveryRecord.js +15 -17
- package/dist/recovery/recoveryRecord.js.map +1 -1
- package/dist/recovery/recoveryWebvh.d.ts.map +1 -1
- package/dist/recovery/recoveryWebvh.js +4 -0
- package/dist/recovery/recoveryWebvh.js.map +1 -1
- package/dist/space/collections.d.ts +2 -2
- package/dist/space/collections.js +2 -2
- package/dist/space/index.d.ts +5 -2
- package/dist/space/index.d.ts.map +1 -1
- package/dist/space/index.js +5 -2
- package/dist/space/index.js.map +1 -1
- package/dist/space/provisioning.d.ts +25 -14
- package/dist/space/provisioning.d.ts.map +1 -1
- package/dist/space/provisioning.js +22 -33
- package/dist/space/provisioning.js.map +1 -1
- package/dist/sync/engine.d.ts +28 -1
- package/dist/sync/engine.d.ts.map +1 -1
- package/dist/sync/engine.js +5 -0
- package/dist/sync/engine.js.map +1 -1
- package/dist/sync/index.d.ts +8 -3
- package/dist/sync/index.d.ts.map +1 -1
- package/dist/sync/index.js +8 -3
- package/dist/sync/index.js.map +1 -1
- package/dist/sync/push.d.ts.map +1 -1
- package/dist/sync/push.js +3 -2
- package/dist/sync/push.js.map +1 -1
- package/dist/sync/remint.d.ts +94 -0
- package/dist/sync/remint.d.ts.map +1 -0
- package/dist/sync/remint.js +111 -0
- package/dist/sync/remint.js.map +1 -0
- package/dist/sync/types.d.ts +38 -4
- package/dist/sync/types.d.ts.map +1 -1
- package/dist/sync/types.js +4 -4
- package/dist/sync/types.js.map +1 -1
- package/dist/webvh/didWebvh.d.ts.map +1 -1
- package/dist/webvh/didWebvh.js +3 -0
- package/dist/webvh/didWebvh.js.map +1 -1
- package/dist/webvh/revokeClient.d.ts.map +1 -1
- package/dist/webvh/revokeClient.js +1 -0
- package/dist/webvh/revokeClient.js.map +1 -1
- package/package.json +5 -5
package/dist/keyring/record.js
CHANGED
|
@@ -1,16 +1,114 @@
|
|
|
1
|
-
import { createEdvDocCipher } from '@interop/was-client/edv';
|
|
1
|
+
import { createEdvDocCipher, initRecipients, ownerRecipient } from '@interop/was-client/edv';
|
|
2
2
|
import { KEYRING_COLLECTION } from '../space/collections.js';
|
|
3
3
|
/**
|
|
4
|
-
* The version stamped on the stored `{ version, wrapped }` keyring
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* re-provisioned, not migrated.
|
|
4
|
+
* The version stamped on the stored `{ version, encryption, wrapped }` keyring
|
|
5
|
+
* envelope: the record whose envelope seals under the record's own key epoch
|
|
6
|
+
* (the `encryption` member). Any other version is refused as unusable -- such
|
|
7
|
+
* accounts are re-provisioned, not migrated.
|
|
8
8
|
*/
|
|
9
|
-
export const KEYRING_RECORD_VERSION =
|
|
9
|
+
export const KEYRING_RECORD_VERSION = 1;
|
|
10
|
+
/**
|
|
11
|
+
* Mints the one-epoch descriptor a fresh record is sealed under: epoch[0]
|
|
12
|
+
* wrapped to the given KAK alone, built through `initRecipients` against a
|
|
13
|
+
* throwaway in-memory store (the descriptor's home is the record itself).
|
|
14
|
+
* Exported for any consumer sealing a self-contained
|
|
15
|
+
* `{ version, encryption, wrapped }` record -- the keyring and recovery
|
|
16
|
+
* records here, and a wallet app's own locally stored records (e.g.
|
|
17
|
+
* freewallet's client-key record and unlock-methods registry).
|
|
18
|
+
*
|
|
19
|
+
* @param options {object}
|
|
20
|
+
* @param options.keyAgreementKey {IKeyAgreementKey} the wrapping KAK (for
|
|
21
|
+
* the keyring record, the unlock KAK)
|
|
22
|
+
* @returns {Promise<CollectionEncryption>}
|
|
23
|
+
*/
|
|
24
|
+
export async function mintRecordEncryption({ keyAgreementKey }) {
|
|
25
|
+
let stored = null;
|
|
26
|
+
const store = {
|
|
27
|
+
async read() {
|
|
28
|
+
return stored ? { descriptor: stored } : null;
|
|
29
|
+
},
|
|
30
|
+
async replace(next) {
|
|
31
|
+
stored = next;
|
|
32
|
+
},
|
|
33
|
+
async create(next) {
|
|
34
|
+
stored = next;
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
return initRecipients({
|
|
38
|
+
store,
|
|
39
|
+
recipients: [ownerRecipient({ keyAgreementKey })]
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Builds the record cipher: an EDV cipher over the record's own descriptor.
|
|
44
|
+
* Shared by the wrap and unwrap paths (and by the recovery record, which
|
|
45
|
+
* reuses the keyring cipher context verbatim); an app's own record kind
|
|
46
|
+
* passes its own `collectionId` so its failures name the record kind. The
|
|
47
|
+
* context labels errors only -- the codec is agnostic to it, so a record
|
|
48
|
+
* kind's real swap protection is its contents validation on unwrap.
|
|
49
|
+
*
|
|
50
|
+
* @param options {object}
|
|
51
|
+
* @param options.keyAgreementKey {IKeyAgreementKey} the wrapping KAK (for
|
|
52
|
+
* the keyring record, the unlock KAK)
|
|
53
|
+
* @param options.keyResolver {IKeyResolver}
|
|
54
|
+
* @param options.encryption {CollectionEncryption} the record's descriptor
|
|
55
|
+
* @param [options.collectionId] {string} the cipher context failures are
|
|
56
|
+
* labeled with; defaults to the keyring context
|
|
57
|
+
* @returns {Promise<DocCipher>}
|
|
58
|
+
*/
|
|
59
|
+
export async function recordCipher({ keyAgreementKey, keyResolver, encryption, collectionId = KEYRING_COLLECTION.id }) {
|
|
60
|
+
return createEdvDocCipher({
|
|
61
|
+
keyAgreementKey,
|
|
62
|
+
keyResolver,
|
|
63
|
+
collectionId,
|
|
64
|
+
encryption
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Validates the common `{ version, encryption, wrapped }` frame of a stored
|
|
69
|
+
* record (keyring or recovery -- `label` names the refusals) and returns its
|
|
70
|
+
* members. Exported so an app's own record kinds open their records through
|
|
71
|
+
* the same frame validation the codec here seals with, rather than re-deriving
|
|
72
|
+
* the version and shape checks.
|
|
73
|
+
*
|
|
74
|
+
* @param options {object}
|
|
75
|
+
* @param options.record {unknown}
|
|
76
|
+
* @param options.label {string} `'keyring'`, `'recovery'`, or an app record
|
|
77
|
+
* kind's own label
|
|
78
|
+
* @param [options.version] {number} the version the frame must carry;
|
|
79
|
+
* defaults to the keyring record version
|
|
80
|
+
* @returns {{ encryption: CollectionEncryption, wrapped: unknown }}
|
|
81
|
+
*/
|
|
82
|
+
export function parseRecordFrame({ record, label, version = KEYRING_RECORD_VERSION }) {
|
|
83
|
+
if (record === null || typeof record !== 'object') {
|
|
84
|
+
throw new Error(`Malformed ${label} record.`);
|
|
85
|
+
}
|
|
86
|
+
const { version: recordVersion, encryption, wrapped } = record;
|
|
87
|
+
if (recordVersion !== version) {
|
|
88
|
+
throw new Error(`Unsupported ${label} record version "${String(recordVersion)}".`);
|
|
89
|
+
}
|
|
90
|
+
if (wrapped === undefined || wrapped === null) {
|
|
91
|
+
throw new Error(`Malformed ${label} record.`);
|
|
92
|
+
}
|
|
93
|
+
// A version-1 frame carrying a wrap but no descriptor is the retired
|
|
94
|
+
// pre-extraction record shape, which shipped under this same version number
|
|
95
|
+
// and wrapped a data seed. It is unusable, not merely damaged -- name the
|
|
96
|
+
// shape so the refusal is not read as corruption.
|
|
97
|
+
if (encryption === undefined && version === KEYRING_RECORD_VERSION) {
|
|
98
|
+
throw new Error(`The ${label} record uses the retired pre-extraction version 1 shape ` +
|
|
99
|
+
'(a data-seed wrap with no encryption descriptor); such accounts are ' +
|
|
100
|
+
're-provisioned, not migrated.');
|
|
101
|
+
}
|
|
102
|
+
if (encryption === null || typeof encryption !== 'object') {
|
|
103
|
+
throw new Error(`The ${label} record is missing its encryption descriptor.`);
|
|
104
|
+
}
|
|
105
|
+
return { encryption: encryption, wrapped };
|
|
106
|
+
}
|
|
10
107
|
/**
|
|
11
108
|
* Wraps the account-pointer contents into a keyring record: the controller,
|
|
12
|
-
* email, and pointer (+ timestamp)
|
|
13
|
-
*
|
|
109
|
+
* email, and pointer (+ timestamp) sealed under a freshly minted record epoch
|
|
110
|
+
* whose key is wrapped to the unlock KAK. Deliberately carries no key material
|
|
111
|
+
* of any kind.
|
|
14
112
|
*
|
|
15
113
|
* @param options {object}
|
|
16
114
|
* @param options.controller {string} the account did:key
|
|
@@ -19,13 +117,15 @@ export const KEYRING_RECORD_VERSION = 2;
|
|
|
19
117
|
* no-WAS deployments)
|
|
20
118
|
* @param options.keyAgreementKey {IKeyAgreementKey} the unlock KAK
|
|
21
119
|
* @param options.keyResolver {IKeyResolver}
|
|
22
|
-
* @returns {Promise<{ version: number,
|
|
120
|
+
* @returns {Promise<{ version: number, encryption: CollectionEncryption,
|
|
121
|
+
* wrapped: unknown }>}
|
|
23
122
|
*/
|
|
24
123
|
export async function wrapKeyringRecord({ controller, email, pointer, keyAgreementKey, keyResolver }) {
|
|
25
|
-
const
|
|
124
|
+
const encryption = await mintRecordEncryption({ keyAgreementKey });
|
|
125
|
+
const cipher = await recordCipher({
|
|
26
126
|
keyAgreementKey,
|
|
27
127
|
keyResolver,
|
|
28
|
-
|
|
128
|
+
encryption
|
|
29
129
|
});
|
|
30
130
|
const { envelope } = await cipher.encrypt({
|
|
31
131
|
data: {
|
|
@@ -43,12 +143,11 @@ export async function wrapKeyringRecord({ controller, email, pointer, keyAgreeme
|
|
|
43
143
|
createdAt: new Date().toISOString()
|
|
44
144
|
}
|
|
45
145
|
});
|
|
46
|
-
return { version: KEYRING_RECORD_VERSION, wrapped: envelope };
|
|
146
|
+
return { version: KEYRING_RECORD_VERSION, encryption, wrapped: envelope };
|
|
47
147
|
}
|
|
48
148
|
/**
|
|
49
149
|
* Unwraps and validates a keyring record. Rejects a record whose `version` is
|
|
50
|
-
* not the current one (
|
|
51
|
-
* seed and are refused -- accounts are re-provisioned, not migrated), and
|
|
150
|
+
* not the current one (accounts are re-provisioned, not migrated), and
|
|
52
151
|
* sanity-checks the decrypted plaintext (non-empty controller, well-formed
|
|
53
152
|
* pointer when present).
|
|
54
153
|
*
|
|
@@ -59,17 +158,14 @@ export async function wrapKeyringRecord({ controller, email, pointer, keyAgreeme
|
|
|
59
158
|
* @returns {Promise<KeyringRecordContents>}
|
|
60
159
|
*/
|
|
61
160
|
export async function unwrapKeyringRecord({ record, keyAgreementKey, keyResolver }) {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
throw new Error(`Unsupported keyring record version "${String(version)}".`);
|
|
68
|
-
}
|
|
69
|
-
const cipher = await createEdvDocCipher({
|
|
161
|
+
const { encryption, wrapped } = parseRecordFrame({
|
|
162
|
+
record,
|
|
163
|
+
label: 'keyring'
|
|
164
|
+
});
|
|
165
|
+
const cipher = await recordCipher({
|
|
70
166
|
keyAgreementKey,
|
|
71
167
|
keyResolver,
|
|
72
|
-
|
|
168
|
+
encryption
|
|
73
169
|
});
|
|
74
170
|
const plaintext = (await cipher.decrypt({
|
|
75
171
|
envelope: wrapped
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"record.js","sourceRoot":"","sources":["../../src/keyring/record.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"record.js","sourceRoot":"","sources":["../../src/keyring/record.ts"],"names":[],"mappings":"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"}
|
package/dist/keys/index.d.ts
CHANGED
|
@@ -36,6 +36,9 @@
|
|
|
36
36
|
* re-epoch that brings an encrypted collection onto the roster's current
|
|
37
37
|
* user key, and the parallel best-effort fan-out over the collections the wallet
|
|
38
38
|
* names (also the completion sweep's driver).
|
|
39
|
+
* - `ensureWalletSpaceEpochs` -- the provision-time epoch[0] install for the
|
|
40
|
+
* wallet Space's encrypted collections, the EDV-bearing second step of
|
|
41
|
+
* `provisionWalletSpace`.
|
|
39
42
|
*/
|
|
40
43
|
export { mintUserKey, userKeyVaultKeys } from './userKey.js';
|
|
41
44
|
export type { UserKey } from './userKey.js';
|
|
@@ -46,6 +49,8 @@ export { cascadeCollectionsToUserKey, userKeyAsRecipient, rotateCollectionEpochs
|
|
|
46
49
|
export type { CollectionUserKeyRotationOutcome, UserKeyCascadeResult } from './userKeyCascade.js';
|
|
47
50
|
export type { UserKeyRosterReadResult, RosterRecipientDocument } from './userKeyRoster.js';
|
|
48
51
|
export { userKeyRosterDescriptorStore } from './rosterStore.js';
|
|
52
|
+
export { ensureWalletSpaceEpochs } from './spaceEpochs.js';
|
|
53
|
+
export type { WalletSpaceEpochsResult } from './spaceEpochs.js';
|
|
49
54
|
export { readClientLabels, removeClientLabel, setClientLabel } from './clientLabels.js';
|
|
50
55
|
export type { ClientLabelsRecord, ClientLabelsStore } from './clientLabels.js';
|
|
51
56
|
export { wasClientLabelsStore } from './wasLabelsStore.js';
|
package/dist/keys/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/keys/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/keys/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAA;AAC5D,YAAY,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AAE3C,OAAO,EACL,6BAA6B,EAC7B,qBAAqB,EACrB,qBAAqB,EACrB,wBAAwB,EACxB,0BAA0B,EAC3B,MAAM,sBAAsB,CAAA;AAC7B,YAAY,EACV,eAAe,EACf,mBAAmB,EACnB,uBAAuB,EACxB,MAAM,sBAAsB,CAAA;AAE7B,OAAO,EACL,yBAAyB,EACzB,+BAA+B,EAC/B,mBAAmB,EACnB,4BAA4B,EAC5B,2BAA2B,EAC3B,wBAAwB,EACxB,yBAAyB,EACzB,8BAA8B,EAC9B,iBAAiB,EACjB,kBAAkB,EAClB,mBAAmB,EACnB,4BAA4B,EAC7B,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EACL,2BAA2B,EAC3B,kBAAkB,EAClB,+BAA+B,EAC/B,wBAAwB,EACzB,MAAM,qBAAqB,CAAA;AAC5B,YAAY,EACV,gCAAgC,EAChC,oBAAoB,EACrB,MAAM,qBAAqB,CAAA;AAC5B,YAAY,EACV,uBAAuB,EACvB,uBAAuB,EACxB,MAAM,oBAAoB,CAAA;AAE3B,OAAO,EAAE,4BAA4B,EAAE,MAAM,kBAAkB,CAAA;AAE/D,OAAO,EAAE,uBAAuB,EAAE,MAAM,kBAAkB,CAAA;AAC1D,YAAY,EAAE,uBAAuB,EAAE,MAAM,kBAAkB,CAAA;AAE/D,OAAO,EACL,gBAAgB,EAChB,iBAAiB,EACjB,cAAc,EACf,MAAM,mBAAmB,CAAA;AAC1B,YAAY,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAA;AAE9E,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAA"}
|
package/dist/keys/index.js
CHANGED
|
@@ -36,12 +36,16 @@
|
|
|
36
36
|
* re-epoch that brings an encrypted collection onto the roster's current
|
|
37
37
|
* user key, and the parallel best-effort fan-out over the collections the wallet
|
|
38
38
|
* names (also the completion sweep's driver).
|
|
39
|
+
* - `ensureWalletSpaceEpochs` -- the provision-time epoch[0] install for the
|
|
40
|
+
* wallet Space's encrypted collections, the EDV-bearing second step of
|
|
41
|
+
* `provisionWalletSpace`.
|
|
39
42
|
*/
|
|
40
43
|
export { mintUserKey, userKeyVaultKeys } from './userKey.js';
|
|
41
44
|
export { assertEnrolledClientKeyRecord, decodeClientKeyRecord, encodeClientKeyRecord, parseClientRecordUserKey, parseClientRecordWebvhKeys } from './clientKeyRecord.js';
|
|
42
45
|
export { addUserKeyRosterRecipient, convergeUserKeyRosterToDocument, ensureUserKeyRoster, UserKeyRosterContinuityError, UserKeyRosterIntegrityError, UserKeyRosterUnwrapError, userKeyRosterEpochsSigner, userKeyRosterRecipientResolver, readUserKeyRoster, rosterRecipientKid, rotateUserKeyRoster, verifyUserKeyRosterEpochsSig } from './userKeyRoster.js';
|
|
43
46
|
export { cascadeCollectionsToUserKey, userKeyAsRecipient, rotateCollectionEpochsToUserKey, unwrapUserKeyGenerations } from './userKeyCascade.js';
|
|
44
47
|
export { userKeyRosterDescriptorStore } from './rosterStore.js';
|
|
48
|
+
export { ensureWalletSpaceEpochs } from './spaceEpochs.js';
|
|
45
49
|
export { readClientLabels, removeClientLabel, setClientLabel } from './clientLabels.js';
|
|
46
50
|
export { wasClientLabelsStore } from './wasLabelsStore.js';
|
|
47
51
|
//# sourceMappingURL=index.js.map
|
package/dist/keys/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/keys/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/keys/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAA;AAG5D,OAAO,EACL,6BAA6B,EAC7B,qBAAqB,EACrB,qBAAqB,EACrB,wBAAwB,EACxB,0BAA0B,EAC3B,MAAM,sBAAsB,CAAA;AAO7B,OAAO,EACL,yBAAyB,EACzB,+BAA+B,EAC/B,mBAAmB,EACnB,4BAA4B,EAC5B,2BAA2B,EAC3B,wBAAwB,EACxB,yBAAyB,EACzB,8BAA8B,EAC9B,iBAAiB,EACjB,kBAAkB,EAClB,mBAAmB,EACnB,4BAA4B,EAC7B,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EACL,2BAA2B,EAC3B,kBAAkB,EAClB,+BAA+B,EAC/B,wBAAwB,EACzB,MAAM,qBAAqB,CAAA;AAU5B,OAAO,EAAE,4BAA4B,EAAE,MAAM,kBAAkB,CAAA;AAE/D,OAAO,EAAE,uBAAuB,EAAE,MAAM,kBAAkB,CAAA;AAG1D,OAAO,EACL,gBAAgB,EAChB,iBAAiB,EACjB,cAAc,EACf,MAAM,mBAAmB,CAAA;AAG1B,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAA"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) 2026 Interop Alliance. All rights reserved.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Provision-time key-epoch install for the wallet Space's encrypted
|
|
6
|
+
* collections: every encrypted collection's descriptor carries an epoch roster
|
|
7
|
+
* from birth (epoch[0] a fresh random epoch key, never a user-key generation),
|
|
8
|
+
* and reads/writes are refused fail-closed until it does. `provisionWalletSpace`
|
|
9
|
+
* (the crypto-free container ensure in `space`) declares the collections; this
|
|
10
|
+
* EDV-bearing second step installs each one's epoch[0], wrapped to the user key
|
|
11
|
+
* (recipient zero). It lives in `keys` rather than `space` so the root barrel,
|
|
12
|
+
* which re-exports `space`, stays free of the EDV crypto graph -- the same
|
|
13
|
+
* split was-client makes between `ensureSpaceAndCollection` and
|
|
14
|
+
* `ensureFirstEpoch`.
|
|
15
|
+
*
|
|
16
|
+
* The install is re-provisioning, never a content migration: `ensureFirstEpoch`
|
|
17
|
+
* installs a fresh epoch[0] onto ANY epoch-less descriptor, with no check for
|
|
18
|
+
* content already in the collection. So any resource sealed before epochs
|
|
19
|
+
* existed -- straight to the user key's key-agreement key, in the shape that
|
|
20
|
+
* predates the epoch roster -- stops being routable once epoch[0] lands
|
|
21
|
+
* (`UnknownEpochError`), and nothing here re-seals it. That is deliberate:
|
|
22
|
+
* epoch-less encrypted content only ever existed in pre-release accounts, whose
|
|
23
|
+
* affected population is effectively zero, and re-provisioning from scratch is
|
|
24
|
+
* the supported answer for them, as it is for a pre-release keyring or recovery
|
|
25
|
+
* record.
|
|
26
|
+
*/
|
|
27
|
+
import type { CollectionEncryption, WasClient } from '@interop/was-client';
|
|
28
|
+
import type { UserKey } from './userKey.js';
|
|
29
|
+
/**
|
|
30
|
+
* What the epoch[0] fan-out did, per collection id: the settled epoch-bearing
|
|
31
|
+
* descriptor of every collection that came through (with whether this call is
|
|
32
|
+
* the one that installed it), and the per-collection failures the caller
|
|
33
|
+
* surfaces -- one stuck collection never discards the others' outcomes.
|
|
34
|
+
*/
|
|
35
|
+
export interface WalletSpaceEpochsResult {
|
|
36
|
+
outcomes: Record<string, {
|
|
37
|
+
installed: boolean;
|
|
38
|
+
descriptor: CollectionEncryption;
|
|
39
|
+
}>;
|
|
40
|
+
failed: Array<{
|
|
41
|
+
collectionId: string;
|
|
42
|
+
error: unknown;
|
|
43
|
+
}>;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Installs key epoch[0] on every encrypted collection of the wallet Space
|
|
47
|
+
* roster (or on the given `collectionIds`), concurrently, wrapped to the user
|
|
48
|
+
* key. Each install is `ensureFirstEpoch`: create-if-absent through the
|
|
49
|
+
* descriptor-store seam, adopting (never overwriting) a roster another
|
|
50
|
+
* provisioner already landed -- so re-running after a tear converges, and
|
|
51
|
+
* exactly one epoch[0] ever exists per collection. Run it after
|
|
52
|
+
* `provisionWalletSpace` has declared the collections; the wallet Space's
|
|
53
|
+
* provisioning is complete only once both steps have.
|
|
54
|
+
*
|
|
55
|
+
* Run both steps from the sync engine's `ensureProvisioned` seam (or, for a
|
|
56
|
+
* driver of its own, equally before the collection's first content push): the
|
|
57
|
+
* descriptor-before-first-content-push invariant rests on it.
|
|
58
|
+
*
|
|
59
|
+
* A collection that fails is reported in `failed` and the rest proceed, so a
|
|
60
|
+
* transient failure on one collection never costs the caller the descriptors
|
|
61
|
+
* the others just settled on. The caller decides what a failure means; a naive
|
|
62
|
+
* full re-run converges, since the collections that did settle are adopted
|
|
63
|
+
* untouched.
|
|
64
|
+
*
|
|
65
|
+
* **Re-minting after adoption.** `installed: false` is not on its own the eager
|
|
66
|
+
* minter's re-mint trigger: it is equally the steady state of every re-run,
|
|
67
|
+
* where nothing changed. The returned `descriptor` is what matters -- an eager
|
|
68
|
+
* minter (one that seals envelopes at local write time against a cached
|
|
69
|
+
* descriptor) builds its cipher from the descriptor returned here, and re-mints
|
|
70
|
+
* every pending envelope that cipher cannot route before pushing.
|
|
71
|
+
* `remintPendingEnvelopes` (`@interop/wallet-core/sync`) is that path; it
|
|
72
|
+
* decides per row from the envelope itself, so running it with the returned
|
|
73
|
+
* descriptor's cipher on every run is both correct and (in the settled case)
|
|
74
|
+
* free.
|
|
75
|
+
*
|
|
76
|
+
* @param options {object}
|
|
77
|
+
* @param options.was {WasClient}
|
|
78
|
+
* @param options.spaceId {string}
|
|
79
|
+
* @param options.userKey {UserKey} the account's user key, epoch[0]'s one
|
|
80
|
+
* initial recipient
|
|
81
|
+
* @param [options.collectionIds] {string[]} the encrypted collections to
|
|
82
|
+
* cover; defaults to the wallet Space roster's encrypted collections. A
|
|
83
|
+
* caller naming its own ids (e.g. `contacts`) must name only collections
|
|
84
|
+
* declared encrypted
|
|
85
|
+
* @returns {Promise<WalletSpaceEpochsResult>} per collection id, the settled
|
|
86
|
+
* descriptor and whether this call installed its epoch[0] (`false` means an
|
|
87
|
+
* existing roster was adopted), plus the collections that failed
|
|
88
|
+
*/
|
|
89
|
+
export declare function ensureWalletSpaceEpochs({ was, spaceId, userKey, collectionIds }: {
|
|
90
|
+
was: WasClient;
|
|
91
|
+
spaceId: string;
|
|
92
|
+
userKey: UserKey;
|
|
93
|
+
collectionIds?: string[];
|
|
94
|
+
}): Promise<WalletSpaceEpochsResult>;
|
|
95
|
+
//# sourceMappingURL=spaceEpochs.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spaceEpochs.d.ts","sourceRoot":"","sources":["../../src/keys/spaceEpochs.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,OAAO,KAAK,EAAE,oBAAoB,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAA;AAK1E,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AAE3C;;;;;GAKG;AACH,MAAM,WAAW,uBAAuB;IACtC,QAAQ,EAAE,MAAM,CACd,MAAM,EACN;QAAE,SAAS,EAAE,OAAO,CAAC;QAAC,UAAU,EAAE,oBAAoB,CAAA;KAAE,CACzD,CAAA;IACD,MAAM,EAAE,KAAK,CAAC;QAAE,YAAY,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,OAAO,CAAA;KAAE,CAAC,CAAA;CACxD;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AACH,wBAAsB,uBAAuB,CAAC,EAC5C,GAAG,EACH,OAAO,EACP,OAAO,EACP,aAAa,EACd,EAAE;IACD,GAAG,EAAE,SAAS,CAAA;IACd,OAAO,EAAE,MAAM,CAAA;IACf,OAAO,EAAE,OAAO,CAAA;IAChB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAA;CACzB,GAAG,OAAO,CAAC,uBAAuB,CAAC,CA6BnC"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { ensureFirstEpoch } from '@interop/was-client/edv';
|
|
2
|
+
import { WALLET_SPACE_PROVISION_ROSTER } from '../space/collections.js';
|
|
3
|
+
import { userKeyAsRecipient } from './userKeyCascade.js';
|
|
4
|
+
/**
|
|
5
|
+
* Installs key epoch[0] on every encrypted collection of the wallet Space
|
|
6
|
+
* roster (or on the given `collectionIds`), concurrently, wrapped to the user
|
|
7
|
+
* key. Each install is `ensureFirstEpoch`: create-if-absent through the
|
|
8
|
+
* descriptor-store seam, adopting (never overwriting) a roster another
|
|
9
|
+
* provisioner already landed -- so re-running after a tear converges, and
|
|
10
|
+
* exactly one epoch[0] ever exists per collection. Run it after
|
|
11
|
+
* `provisionWalletSpace` has declared the collections; the wallet Space's
|
|
12
|
+
* provisioning is complete only once both steps have.
|
|
13
|
+
*
|
|
14
|
+
* Run both steps from the sync engine's `ensureProvisioned` seam (or, for a
|
|
15
|
+
* driver of its own, equally before the collection's first content push): the
|
|
16
|
+
* descriptor-before-first-content-push invariant rests on it.
|
|
17
|
+
*
|
|
18
|
+
* A collection that fails is reported in `failed` and the rest proceed, so a
|
|
19
|
+
* transient failure on one collection never costs the caller the descriptors
|
|
20
|
+
* the others just settled on. The caller decides what a failure means; a naive
|
|
21
|
+
* full re-run converges, since the collections that did settle are adopted
|
|
22
|
+
* untouched.
|
|
23
|
+
*
|
|
24
|
+
* **Re-minting after adoption.** `installed: false` is not on its own the eager
|
|
25
|
+
* minter's re-mint trigger: it is equally the steady state of every re-run,
|
|
26
|
+
* where nothing changed. The returned `descriptor` is what matters -- an eager
|
|
27
|
+
* minter (one that seals envelopes at local write time against a cached
|
|
28
|
+
* descriptor) builds its cipher from the descriptor returned here, and re-mints
|
|
29
|
+
* every pending envelope that cipher cannot route before pushing.
|
|
30
|
+
* `remintPendingEnvelopes` (`@interop/wallet-core/sync`) is that path; it
|
|
31
|
+
* decides per row from the envelope itself, so running it with the returned
|
|
32
|
+
* descriptor's cipher on every run is both correct and (in the settled case)
|
|
33
|
+
* free.
|
|
34
|
+
*
|
|
35
|
+
* @param options {object}
|
|
36
|
+
* @param options.was {WasClient}
|
|
37
|
+
* @param options.spaceId {string}
|
|
38
|
+
* @param options.userKey {UserKey} the account's user key, epoch[0]'s one
|
|
39
|
+
* initial recipient
|
|
40
|
+
* @param [options.collectionIds] {string[]} the encrypted collections to
|
|
41
|
+
* cover; defaults to the wallet Space roster's encrypted collections. A
|
|
42
|
+
* caller naming its own ids (e.g. `contacts`) must name only collections
|
|
43
|
+
* declared encrypted
|
|
44
|
+
* @returns {Promise<WalletSpaceEpochsResult>} per collection id, the settled
|
|
45
|
+
* descriptor and whether this call installed its epoch[0] (`false` means an
|
|
46
|
+
* existing roster was adopted), plus the collections that failed
|
|
47
|
+
*/
|
|
48
|
+
export async function ensureWalletSpaceEpochs({ was, spaceId, userKey, collectionIds }) {
|
|
49
|
+
const ids = collectionIds ??
|
|
50
|
+
WALLET_SPACE_PROVISION_ROSTER.filter(spec => spec.encryption === 'edv').map(spec => spec.collectionId);
|
|
51
|
+
const outcomes = {};
|
|
52
|
+
const failed = [];
|
|
53
|
+
await Promise.all(ids.map(async (collectionId) => {
|
|
54
|
+
try {
|
|
55
|
+
const { installed, descriptor } = await ensureFirstEpoch({
|
|
56
|
+
collection: was.space(spaceId).collection(collectionId),
|
|
57
|
+
recipients: [userKeyAsRecipient({ userKey })]
|
|
58
|
+
});
|
|
59
|
+
outcomes[collectionId] = { installed, descriptor };
|
|
60
|
+
}
|
|
61
|
+
catch (err) {
|
|
62
|
+
failed.push({
|
|
63
|
+
collectionId,
|
|
64
|
+
error: new Error(`Error installing the first key epoch for collection ` +
|
|
65
|
+
`"${collectionId}" in space "${spaceId}".`, { cause: err })
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
}));
|
|
69
|
+
return { outcomes, failed };
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=spaceEpochs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spaceEpochs.js","sourceRoot":"","sources":["../../src/keys/spaceEpochs.ts"],"names":[],"mappings":"AA2BA,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAA;AAE1D,OAAO,EAAE,6BAA6B,EAAE,MAAM,yBAAyB,CAAA;AACvE,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAA;AAiBxD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AACH,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAAC,EAC5C,GAAG,EACH,OAAO,EACP,OAAO,EACP,aAAa,EAMd;IACC,MAAM,GAAG,GACP,aAAa;QACb,6BAA6B,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,UAAU,KAAK,KAAK,CAAC,CAAC,GAAG,CACzE,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,YAAY,CAC1B,CAAA;IACH,MAAM,QAAQ,GAAwC,EAAE,CAAA;IACxD,MAAM,MAAM,GAAoD,EAAE,CAAA;IAClE,MAAM,OAAO,CAAC,GAAG,CACf,GAAG,CAAC,GAAG,CAAC,KAAK,EAAC,YAAY,EAAC,EAAE;QAC3B,IAAI,CAAC;YACH,MAAM,EAAE,SAAS,EAAE,UAAU,EAAE,GAAG,MAAM,gBAAgB,CAAC;gBACvD,UAAU,EAAE,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,UAAU,CAAC,YAAY,CAAC;gBACvD,UAAU,EAAE,CAAC,kBAAkB,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC;aAC9C,CAAC,CAAA;YACF,QAAQ,CAAC,YAAY,CAAC,GAAG,EAAE,SAAS,EAAE,UAAU,EAAE,CAAA;QACpD,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,CAAC,IAAI,CAAC;gBACV,YAAY;gBACZ,KAAK,EAAE,IAAI,KAAK,CACd,sDAAsD;oBACpD,IAAI,YAAY,eAAe,OAAO,IAAI,EAC5C,EAAE,KAAK,EAAE,GAAG,EAAE,CACf;aACF,CAAC,CAAA;QACJ,CAAC;IACH,CAAC,CAAC,CACH,CAAA;IACD,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAA;AAC7B,CAAC"}
|
|
@@ -19,12 +19,13 @@
|
|
|
19
19
|
* unwrapUserKeyGenerations}) -- needed both to recognize a stale epoch and to
|
|
20
20
|
* escrow the fresh user key into a stranded collection's history.
|
|
21
21
|
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
22
|
+
* The cascade is rotation-only: every encrypted collection's descriptor
|
|
23
|
+
* carries an epoch roster from provisioning (`ensureWalletSpaceEpochs`), so a
|
|
24
|
+
* descriptor met without epochs can only mean a tampering or pre-provisioning
|
|
25
|
+
* host and is refused fail-closed. No construction anywhere installs a
|
|
26
|
+
* user-key secret as a collection epoch secret, which is what keeps a
|
|
27
|
+
* collection-epoch escrow (an App Connect grant, a share) from ever handing a
|
|
28
|
+
* grantee the user key itself.
|
|
28
29
|
*/
|
|
29
30
|
import type { IKeyAgreementKey } from '@interop/data-integrity-core';
|
|
30
31
|
import type { CollectionEncryption } from '@interop/was-client';
|
|
@@ -64,21 +65,15 @@ export declare function unwrapUserKeyGenerations({ descriptor, clientKeyAgreemen
|
|
|
64
65
|
}): Promise<UserKey[]>;
|
|
65
66
|
/**
|
|
66
67
|
* What one collection's cascade step did: `noop` (already on the current user
|
|
67
|
-
* key), `
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
* `rotated` (a fresh epoch sealed to the current user key, the stale
|
|
71
|
-
* generations retired -- the pre-epoch install falls through to this).
|
|
68
|
+
* key), `escrowed` (the current user key's wrap was completed into history
|
|
69
|
+
* with no stale epoch to rotate), or `rotated` (a fresh epoch sealed to the
|
|
70
|
+
* current user key, the stale generations retired).
|
|
72
71
|
*/
|
|
73
|
-
export type CollectionUserKeyRotationOutcome = 'noop' | '
|
|
72
|
+
export type CollectionUserKeyRotationOutcome = 'noop' | 'escrowed' | 'rotated';
|
|
74
73
|
/**
|
|
75
74
|
* Brings ONE encrypted collection's epoch roster onto the current user key --
|
|
76
75
|
* the per-collection op of the revocation cascade and of the completion sweep:
|
|
77
76
|
*
|
|
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 user key,
|
|
80
|
-
* then fall through to the rotation. A first-generation account (nothing
|
|
81
|
-
* prior) installs the current user key alone and is done.
|
|
82
77
|
* - **Stale current epoch** (names a non-current generation): one
|
|
83
78
|
* `replaceRecipient` write -- the current user key escrowed into every epoch,
|
|
84
79
|
* a fresh epoch minted without the stale generations. Two requests per
|
|
@@ -86,6 +81,9 @@ export type CollectionUserKeyRotationOutcome = 'noop' | 'installed' | 'escrowed'
|
|
|
86
81
|
* default did:key resolver re-wraps them).
|
|
87
82
|
* - **Current already** and fully escrowed: no write at all, so a naive
|
|
88
83
|
* re-run after a mid-cascade crash converges with zero redundant epochs.
|
|
84
|
+
* - **No epochs**: refused fail-closed (see the module doc) -- provisioning
|
|
85
|
+
* installs every encrypted collection's epoch[0], so the cascade never
|
|
86
|
+
* mints a first epoch.
|
|
89
87
|
*
|
|
90
88
|
* The pull axis is deliberately a no-op here: a user key rotation follows a
|
|
91
89
|
* document edit (client revocation, code retirement) that already killed the
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"userKeyCascade.d.ts","sourceRoot":"","sources":["../../src/keys/userKeyCascade.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH
|
|
1
|
+
{"version":3,"file":"userKeyCascade.d.ts","sourceRoot":"","sources":["../../src/keys/userKeyCascade.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAA;AACpE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAA;AAC/D,OAAO,EAKL,KAAK,yBAAyB,EAC9B,KAAK,kBAAkB,EACxB,MAAM,yBAAyB,CAAA;AAChC,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;;;;;GAKG;AACH,MAAM,MAAM,gCAAgC,GAAG,MAAM,GAAG,UAAU,GAAG,SAAS,CAAA;AAE9E;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;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,CAgG5C;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,5 +1,4 @@
|
|
|
1
|
-
import { addRecipient, epochKeyIdFor,
|
|
2
|
-
import { ValidationError } from '@interop/was-client';
|
|
1
|
+
import { addRecipient, epochKeyIdFor, replaceRecipient, unwrapEpochSecret } from '@interop/was-client/edv';
|
|
3
2
|
import { userKeyVaultKeys } from './userKey.js';
|
|
4
3
|
/**
|
|
5
4
|
* A user key presented as an epoch-roster recipient: the kid is the
|
|
@@ -53,10 +52,6 @@ export async function unwrapUserKeyGenerations({ descriptor, clientKeyAgreementK
|
|
|
53
52
|
* Brings ONE encrypted collection's epoch roster onto the current user key --
|
|
54
53
|
* the per-collection op of the revocation cascade and of the completion sweep:
|
|
55
54
|
*
|
|
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 user key,
|
|
58
|
-
* then fall through to the rotation. A first-generation account (nothing
|
|
59
|
-
* prior) installs the current user key alone and is done.
|
|
60
55
|
* - **Stale current epoch** (names a non-current generation): one
|
|
61
56
|
* `replaceRecipient` write -- the current user key escrowed into every epoch,
|
|
62
57
|
* a fresh epoch minted without the stale generations. Two requests per
|
|
@@ -64,6 +59,9 @@ export async function unwrapUserKeyGenerations({ descriptor, clientKeyAgreementK
|
|
|
64
59
|
* default did:key resolver re-wraps them).
|
|
65
60
|
* - **Current already** and fully escrowed: no write at all, so a naive
|
|
66
61
|
* re-run after a mid-cascade crash converges with zero redundant epochs.
|
|
62
|
+
* - **No epochs**: refused fail-closed (see the module doc) -- provisioning
|
|
63
|
+
* installs every encrypted collection's epoch[0], so the cascade never
|
|
64
|
+
* mints a first epoch.
|
|
67
65
|
*
|
|
68
66
|
* The pull axis is deliberately a no-op here: a user key rotation follows a
|
|
69
67
|
* document edit (client revocation, code retirement) that already killed the
|
|
@@ -83,45 +81,13 @@ export async function rotateCollectionEpochsToUserKey({ store, userKey, generati
|
|
|
83
81
|
if (current === null) {
|
|
84
82
|
return 'noop';
|
|
85
83
|
}
|
|
86
|
-
|
|
84
|
+
const descriptor = current.descriptor;
|
|
87
85
|
const staleGenerations = generations.filter(generation => generation.id !== userKey.id);
|
|
88
86
|
if (!descriptor.epochs?.length || !descriptor.currentEpoch) {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
try {
|
|
94
|
-
const installed = await initRecipients({
|
|
95
|
-
store,
|
|
96
|
-
recipients: previous
|
|
97
|
-
? [
|
|
98
|
-
userKeyAsRecipient({ userKey: previous }),
|
|
99
|
-
userKeyAsRecipient({ userKey })
|
|
100
|
-
]
|
|
101
|
-
: [userKeyAsRecipient({ userKey })],
|
|
102
|
-
epoch: previous
|
|
103
|
-
? { epochId: previous.id, secret: previous.secret }
|
|
104
|
-
: { epochId: userKey.id, secret: userKey.secret }
|
|
105
|
-
});
|
|
106
|
-
if (!previous) {
|
|
107
|
-
// A first-generation account: nothing prior to escrow, nothing to
|
|
108
|
-
// retire, so the install IS the whole step.
|
|
109
|
-
return 'installed';
|
|
110
|
-
}
|
|
111
|
-
descriptor = installed;
|
|
112
|
-
}
|
|
113
|
-
catch (err) {
|
|
114
|
-
// A concurrent cascade won the first-epoch race; re-read and continue
|
|
115
|
-
// into the rotation below against whatever it installed.
|
|
116
|
-
if (!(err instanceof ValidationError)) {
|
|
117
|
-
throw err;
|
|
118
|
-
}
|
|
119
|
-
const reread = await store.read();
|
|
120
|
-
if (reread === null) {
|
|
121
|
-
throw err;
|
|
122
|
-
}
|
|
123
|
-
descriptor = reread.descriptor;
|
|
124
|
-
}
|
|
87
|
+
throw new Error('The collection descriptor carries no key epochs. Every encrypted ' +
|
|
88
|
+
'collection installs its epoch[0] at provision time, so an ' +
|
|
89
|
+
'epoch-less descriptor can only come from a tampering or ' +
|
|
90
|
+
'pre-provisioning host; refusing to rotate it.');
|
|
125
91
|
}
|
|
126
92
|
const currentEpoch = descriptor.epochs.find(epoch => epoch.id === descriptor.currentEpoch) ??
|
|
127
93
|
descriptor.epochs[descriptor.epochs.length - 1];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"userKeyCascade.js","sourceRoot":"","sources":["../../src/keys/userKeyCascade.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"userKeyCascade.js","sourceRoot":"","sources":["../../src/keys/userKeyCascade.ts"],"names":[],"mappings":"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"}
|