mindoodb 0.0.51 → 0.0.53
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-server.md +11 -2
- package/dist/appendonlystores/network/ClientNetworkContentAddressedStore.d.ts +13 -1
- package/dist/appendonlystores/network/ClientNetworkContentAddressedStore.d.ts.map +1 -1
- package/dist/appendonlystores/network/ClientNetworkContentAddressedStore.js +45 -20
- package/dist/appendonlystores/network/ClientNetworkContentAddressedStore.js.map +1 -1
- package/dist/appendonlystores/network/HttpTransport.d.ts +2 -0
- package/dist/appendonlystores/network/HttpTransport.d.ts.map +1 -1
- package/dist/appendonlystores/network/HttpTransport.js +6 -62
- package/dist/appendonlystores/network/HttpTransport.js.map +1 -1
- package/dist/appendonlystores/network/ServerNetworkContentAddressedStore.d.ts +24 -0
- package/dist/appendonlystores/network/ServerNetworkContentAddressedStore.d.ts.map +1 -1
- package/dist/appendonlystores/network/ServerNetworkContentAddressedStore.js +75 -24
- package/dist/appendonlystores/network/ServerNetworkContentAddressedStore.js.map +1 -1
- package/dist/browser/index.mjs +57 -0
- package/dist/core/BaseMindooDB.d.ts +85 -10
- package/dist/core/BaseMindooDB.d.ts.map +1 -1
- package/dist/core/BaseMindooDB.js +687 -196
- package/dist/core/BaseMindooDB.js.map +1 -1
- package/dist/core/BaseMindooTenant.d.ts +59 -7
- package/dist/core/BaseMindooTenant.d.ts.map +1 -1
- package/dist/core/BaseMindooTenant.js +407 -86
- package/dist/core/BaseMindooTenant.js.map +1 -1
- package/dist/core/BaseMindooTenantDirectory.d.ts +61 -9
- package/dist/core/BaseMindooTenantDirectory.d.ts.map +1 -1
- package/dist/core/BaseMindooTenantDirectory.js +363 -35
- package/dist/core/BaseMindooTenantDirectory.js.map +1 -1
- package/dist/core/BaseMindooTenantFactory.d.ts +17 -1
- package/dist/core/BaseMindooTenantFactory.d.ts.map +1 -1
- package/dist/core/BaseMindooTenantFactory.js +286 -17
- package/dist/core/BaseMindooTenantFactory.js.map +1 -1
- package/dist/core/accesscontrol/DirectoryStateNode.d.ts +68 -1
- package/dist/core/accesscontrol/DirectoryStateNode.d.ts.map +1 -1
- package/dist/core/accesscontrol/DirectoryStateNode.js +157 -4
- package/dist/core/accesscontrol/DirectoryStateNode.js.map +1 -1
- package/dist/core/accesscontrol/directoryProjection.d.ts.map +1 -1
- package/dist/core/accesscontrol/directoryProjection.js +14 -5
- package/dist/core/accesscontrol/directoryProjection.js.map +1 -1
- package/dist/core/accesscontrol/grantKeys.d.ts +17 -14
- package/dist/core/accesscontrol/grantKeys.d.ts.map +1 -1
- package/dist/core/accesscontrol/grantKeys.js +51 -21
- package/dist/core/accesscontrol/grantKeys.js.map +1 -1
- package/dist/core/accesscontrol/types.d.ts +2 -1
- package/dist/core/accesscontrol/types.d.ts.map +1 -1
- package/dist/core/accesscontrol/types.js +3 -2
- package/dist/core/accesscontrol/types.js.map +1 -1
- package/dist/core/appendonlystores/network/AuthenticationService.d.ts +7 -0
- package/dist/core/appendonlystores/network/AuthenticationService.d.ts.map +1 -1
- package/dist/core/appendonlystores/network/AuthenticationService.js +70 -14
- package/dist/core/appendonlystores/network/AuthenticationService.js.map +1 -1
- package/dist/core/appendonlystores/network/entryWireCodec.d.ts +51 -0
- package/dist/core/appendonlystores/network/entryWireCodec.d.ts.map +1 -0
- package/dist/core/appendonlystores/network/entryWireCodec.js +93 -0
- package/dist/core/appendonlystores/network/entryWireCodec.js.map +1 -0
- package/dist/core/builtinDbInvariants.d.ts +53 -0
- package/dist/core/builtinDbInvariants.d.ts.map +1 -0
- package/dist/core/builtinDbInvariants.js +147 -0
- package/dist/core/builtinDbInvariants.js.map +1 -0
- package/dist/core/copy/feasibility.d.ts.map +1 -1
- package/dist/core/copy/feasibility.js +3 -0
- package/dist/core/copy/feasibility.js.map +1 -1
- package/dist/core/copy/flatten.d.ts.map +1 -1
- package/dist/core/copy/flatten.js +6 -1
- package/dist/core/copy/flatten.js.map +1 -1
- package/dist/core/copy/host.d.ts +5 -0
- package/dist/core/copy/host.d.ts.map +1 -1
- package/dist/core/copy/preflight.d.ts.map +1 -1
- package/dist/core/copy/preflight.js +7 -1
- package/dist/core/copy/preflight.js.map +1 -1
- package/dist/core/copy/types.d.ts +7 -0
- package/dist/core/copy/types.d.ts.map +1 -1
- package/dist/core/crypto/EntrySignature.d.ts +8 -2
- package/dist/core/crypto/EntrySignature.d.ts.map +1 -1
- package/dist/core/crypto/EntrySignature.js +14 -5
- package/dist/core/crypto/EntrySignature.js.map +1 -1
- package/dist/core/crypto/MindooDocSigner.d.ts.map +1 -1
- package/dist/core/crypto/MindooDocSigner.js +11 -2
- package/dist/core/crypto/MindooDocSigner.js.map +1 -1
- package/dist/core/crypto/RSAEncryption.d.ts +19 -0
- package/dist/core/crypto/RSAEncryption.d.ts.map +1 -1
- package/dist/core/crypto/RSAEncryption.js +36 -3
- package/dist/core/crypto/RSAEncryption.js.map +1 -1
- package/dist/core/crypto/privateKeyEncryption.js +1 -1
- package/dist/core/crypto/privateKeyEncryption.js.map +1 -1
- package/dist/core/crypto/rsaOaep3072.d.ts +12 -0
- package/dist/core/crypto/rsaOaep3072.d.ts.map +1 -0
- package/dist/core/crypto/rsaOaep3072.js +30 -0
- package/dist/core/crypto/rsaOaep3072.js.map +1 -0
- package/dist/core/index.d.ts +7 -3
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +30 -4
- package/dist/core/index.js.map +1 -1
- package/dist/core/join/deviceJoinBootstrap.d.ts +23 -0
- package/dist/core/join/deviceJoinBootstrap.d.ts.map +1 -0
- package/dist/core/join/deviceJoinBootstrap.js +37 -0
- package/dist/core/join/deviceJoinBootstrap.js.map +1 -0
- package/dist/core/keys/KeyBag.d.ts +20 -2
- package/dist/core/keys/KeyBag.d.ts.map +1 -1
- package/dist/core/keys/KeyBag.js +22 -10
- package/dist/core/keys/KeyBag.js.map +1 -1
- package/dist/core/logging/MindooLogger.d.ts.map +1 -1
- package/dist/core/logging/MindooLogger.js +65 -40
- package/dist/core/logging/MindooLogger.js.map +1 -1
- package/dist/core/tenantIdValidation.d.ts.map +1 -1
- package/dist/core/tenantIdValidation.js +1 -1
- package/dist/core/tenantIdValidation.js.map +1 -1
- package/dist/core/tenantSetup.d.ts +94 -9
- package/dist/core/tenantSetup.d.ts.map +1 -1
- package/dist/core/tenantSetup.js +208 -20
- package/dist/core/tenantSetup.js.map +1 -1
- package/dist/core/types.d.ts +295 -33
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js +14 -1
- package/dist/core/types.js.map +1 -1
- package/dist/core/uri/joinRequestUri.d.ts.map +1 -1
- package/dist/core/uri/joinRequestUri.js +12 -3
- package/dist/core/uri/joinRequestUri.js.map +1 -1
- package/dist/core/userid/canonicalUsername.d.ts +66 -0
- package/dist/core/userid/canonicalUsername.d.ts.map +1 -0
- package/dist/core/userid/canonicalUsername.js +235 -0
- package/dist/core/userid/canonicalUsername.js.map +1 -0
- package/dist/core/userid/index.d.ts +9 -0
- package/dist/core/userid/index.d.ts.map +1 -1
- package/dist/core/userid/index.js +14 -0
- package/dist/core/userid/index.js.map +1 -1
- package/dist/core/userkeys/UserKeyDocument.d.ts +50 -0
- package/dist/core/userkeys/UserKeyDocument.d.ts.map +1 -0
- package/dist/core/userkeys/UserKeyDocument.js +145 -0
- package/dist/core/userkeys/UserKeyDocument.js.map +1 -0
- package/dist/core/userkeys/UserKeyManager.d.ts +170 -0
- package/dist/core/userkeys/UserKeyManager.d.ts.map +1 -0
- package/dist/core/userkeys/UserKeyManager.js +1277 -0
- package/dist/core/userkeys/UserKeyManager.js.map +1 -0
- package/dist/core/userkeys/fingerprint.d.ts +17 -0
- package/dist/core/userkeys/fingerprint.d.ts.map +1 -0
- package/dist/core/userkeys/fingerprint.js +59 -0
- package/dist/core/userkeys/fingerprint.js.map +1 -0
- package/dist/core/userkeys/index.d.ts +11 -0
- package/dist/core/userkeys/index.d.ts.map +1 -0
- package/dist/core/userkeys/index.js +49 -0
- package/dist/core/userkeys/index.js.map +1 -0
- package/dist/core/userkeys/recipients.d.ts +51 -0
- package/dist/core/userkeys/recipients.d.ts.map +1 -0
- package/dist/core/userkeys/recipients.js +292 -0
- package/dist/core/userkeys/recipients.js.map +1 -0
- package/dist/core/userkeys/resolveUserKeyDocument.d.ts +21 -0
- package/dist/core/userkeys/resolveUserKeyDocument.d.ts.map +1 -0
- package/dist/core/userkeys/resolveUserKeyDocument.js +98 -0
- package/dist/core/userkeys/resolveUserKeyDocument.js.map +1 -0
- package/dist/core/userkeys/sealedCrypto.d.ts +21 -0
- package/dist/core/userkeys/sealedCrypto.d.ts.map +1 -0
- package/dist/core/userkeys/sealedCrypto.js +79 -0
- package/dist/core/userkeys/sealedCrypto.js.map +1 -0
- package/dist/core/userkeys/sealedTypes.d.ts +72 -0
- package/dist/core/userkeys/sealedTypes.d.ts.map +1 -0
- package/dist/core/userkeys/sealedTypes.js +14 -0
- package/dist/core/userkeys/sealedTypes.js.map +1 -0
- package/dist/core/userkeys/types.d.ts +70 -0
- package/dist/core/userkeys/types.d.ts.map +1 -0
- package/dist/core/userkeys/types.js +48 -0
- package/dist/core/userkeys/types.js.map +1 -0
- package/dist/core/userkeys/validateUserKeyDocument.d.ts +19 -0
- package/dist/core/userkeys/validateUserKeyDocument.d.ts.map +1 -0
- package/dist/core/userkeys/validateUserKeyDocument.js +42 -0
- package/dist/core/userkeys/validateUserKeyDocument.js.map +1 -0
- package/dist/node/server/CapabilityMatcher.d.ts +5 -0
- package/dist/node/server/CapabilityMatcher.d.ts.map +1 -1
- package/dist/node/server/CapabilityMatcher.js +10 -1
- package/dist/node/server/CapabilityMatcher.js.map +1 -1
- package/dist/node/server/DeviceDiscoveryService.d.ts +36 -0
- package/dist/node/server/DeviceDiscoveryService.d.ts.map +1 -0
- package/dist/node/server/DeviceDiscoveryService.js +165 -0
- package/dist/node/server/DeviceDiscoveryService.js.map +1 -0
- package/dist/node/server/MindooDBServer.d.ts +13 -0
- package/dist/node/server/MindooDBServer.d.ts.map +1 -1
- package/dist/node/server/MindooDBServer.js +99 -61
- package/dist/node/server/MindooDBServer.js.map +1 -1
- package/dist/node/server/SystemAdminAuth.d.ts +7 -0
- package/dist/node/server/SystemAdminAuth.d.ts.map +1 -1
- package/dist/node/server/SystemAdminAuth.js +4 -1
- package/dist/node/server/SystemAdminAuth.js.map +1 -1
- package/dist/node/server/TenantManager.d.ts +29 -0
- package/dist/node/server/TenantManager.d.ts.map +1 -1
- package/dist/node/server/TenantManager.js +146 -7
- package/dist/node/server/TenantManager.js.map +1 -1
- package/package.json +5 -5
|
@@ -6,6 +6,7 @@ const timeSource_1 = require("./utils/timeSource");
|
|
|
6
6
|
const logging_1 = require("./logging");
|
|
7
7
|
const grantKeys_1 = require("./accesscontrol/grantKeys");
|
|
8
8
|
const types_2 = require("./accesscontrol/types");
|
|
9
|
+
const DirectoryStateNode_1 = require("./accesscontrol/DirectoryStateNode");
|
|
9
10
|
const DirectoryTimeTravelIndex_1 = require("./accesscontrol/DirectoryTimeTravelIndex");
|
|
10
11
|
const directoryProjection_1 = require("./accesscontrol/directoryProjection");
|
|
11
12
|
const types_3 = require("./accesscontrol/types");
|
|
@@ -113,7 +114,7 @@ class BaseMindooTenantDirectory {
|
|
|
113
114
|
}
|
|
114
115
|
return this.directoryDB;
|
|
115
116
|
}
|
|
116
|
-
async registerUser(userId, administrationPrivateKey, administrationPrivateKeyPassword, userDetails, label) {
|
|
117
|
+
async registerUser(userId, administrationPrivateKey, administrationPrivateKeyPassword, userDetails, label, options) {
|
|
117
118
|
this.logger.info(`Registering user: ${userId.username}`);
|
|
118
119
|
// Check if user with same username (case-insensitive) already has ACTIVE
|
|
119
120
|
// access. A grant whose keys were removed (revocation, §6.5) is ignored
|
|
@@ -131,7 +132,7 @@ class BaseMindooTenantDirectory {
|
|
|
131
132
|
const keysMatch = (0, grantKeys_1.extractSigningPublicKeys)(existingData).includes(userId.userSigningPublicKey) &&
|
|
132
133
|
(0, grantKeys_1.extractEncryptionPublicKeys)(existingData).includes(userId.userEncryptionPublicKey);
|
|
133
134
|
if (keysMatch) {
|
|
134
|
-
// Same user with same keys - skip re-registration
|
|
135
|
+
// Same user with same keys - skip re-registration and re-wrap
|
|
135
136
|
this.logger.debug(`User ${userId.username} already registered with same keys, skipping`);
|
|
136
137
|
return;
|
|
137
138
|
}
|
|
@@ -141,10 +142,11 @@ class BaseMindooTenantDirectory {
|
|
|
141
142
|
const newPair = {
|
|
142
143
|
signingPublicKey: userId.userSigningPublicKey,
|
|
143
144
|
encryptionPublicKey: userId.userEncryptionPublicKey,
|
|
145
|
+
addedAt: (0, timeSource_1.semanticNow)(),
|
|
144
146
|
};
|
|
145
147
|
if (trimmedLabel.length > 0)
|
|
146
148
|
newPair.label = trimmedLabel;
|
|
147
|
-
await this.addUserKeys(userId.username, [newPair], administrationPrivateKey, administrationPrivateKeyPassword);
|
|
149
|
+
await this.addUserKeys(userId.username, [newPair], administrationPrivateKey, administrationPrivateKeyPassword, options?.distributeKeyIds);
|
|
148
150
|
return;
|
|
149
151
|
}
|
|
150
152
|
// Cast to BaseMindooTenant to access protected methods
|
|
@@ -202,6 +204,7 @@ class BaseMindooTenantDirectory {
|
|
|
202
204
|
const initialPair = {
|
|
203
205
|
signingPublicKey: userId.userSigningPublicKey,
|
|
204
206
|
encryptionPublicKey: userId.userEncryptionPublicKey,
|
|
207
|
+
addedAt: (0, timeSource_1.semanticNow)(),
|
|
205
208
|
};
|
|
206
209
|
if (trimmedLabel.length > 0)
|
|
207
210
|
initialPair.label = trimmedLabel;
|
|
@@ -216,6 +219,91 @@ class BaseMindooTenantDirectory {
|
|
|
216
219
|
throw error;
|
|
217
220
|
}
|
|
218
221
|
this.logger.info(`Registered user: ${userId.username}`);
|
|
222
|
+
// Push selected keys (default: tenant default) via key distribution so the
|
|
223
|
+
// joining device can bootstrap without a join-response URI once the
|
|
224
|
+
// server delivers `$publicinfos` (docs/accesscontrol.md §13).
|
|
225
|
+
const distributeKeyIds = options?.distributeKeyIds ?? [types_1.DEFAULT_TENANT_KEY_ID];
|
|
226
|
+
await this.autoDistributeKeysToUser(userId.username, distributeKeyIds, administrationPrivateKey, administrationPrivateKeyPassword);
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Merge `username` into `acl_keydistribution_<keyId>` for each distributable
|
|
230
|
+
* key id (wraps to the person's published User-Key). Skips protected ids and
|
|
231
|
+
* keys the caller's KeyBag does not hold. Used after grant so
|
|
232
|
+
* discover-bootstrap can import `default` without a join-response URI.
|
|
233
|
+
*/
|
|
234
|
+
async autoDistributeKeysToUser(username, keyIds, administrationPrivateKey, administrationPrivateKeyPassword, publicKeyOverride) {
|
|
235
|
+
const filtered = [
|
|
236
|
+
...new Set(keyIds
|
|
237
|
+
.map((id) => (typeof id === "string" ? id.trim() : ""))
|
|
238
|
+
.filter((id) => id.length > 0 && !types_3.PROTECTED_DISTRIBUTION_KEY_IDS.includes(id))),
|
|
239
|
+
];
|
|
240
|
+
if (filtered.length === 0)
|
|
241
|
+
return;
|
|
242
|
+
const baseTenant = this.tenant;
|
|
243
|
+
const preparedBy = baseTenant.getAdministrationPublicKey();
|
|
244
|
+
for (const keyId of filtered) {
|
|
245
|
+
const keyVersions = await this.getKeyVersionManifest(keyId);
|
|
246
|
+
if (keyVersions.length === 0) {
|
|
247
|
+
this.logger.debug(`autoDistributeKeysToUser: skipping "${keyId}" — caller KeyBag does not hold it`);
|
|
248
|
+
continue;
|
|
249
|
+
}
|
|
250
|
+
const existing = (await this.listKeyDistributions()).find((v) => v.keyId === keyId);
|
|
251
|
+
const pushtoNames = new Set((existing?.pushtoUsernames ?? [])
|
|
252
|
+
.map((u) => u.trim())
|
|
253
|
+
.filter((u) => u.length > 0));
|
|
254
|
+
pushtoNames.add(username.trim());
|
|
255
|
+
const usernameLower = username.trim().toLowerCase();
|
|
256
|
+
const pullfromNames = (existing?.pullfromUsernames ?? [])
|
|
257
|
+
.map((u) => u.trim())
|
|
258
|
+
.filter((u) => u.length > 0 && u.toLowerCase() !== usernameLower);
|
|
259
|
+
const pushto = [];
|
|
260
|
+
for (const pushtoUsername of pushtoNames) {
|
|
261
|
+
const override = pushtoUsername.trim() === username.trim() ? publicKeyOverride : undefined;
|
|
262
|
+
const wrapped = await this.wrapKeyForUser(keyId, pushtoUsername, override);
|
|
263
|
+
if (wrapped)
|
|
264
|
+
pushto.push(wrapped);
|
|
265
|
+
}
|
|
266
|
+
if (pushto.length === 0) {
|
|
267
|
+
this.logger.warn(`autoDistributeKeysToUser: skipping "${keyId}" — no published User-Key wrap yet for "${username}"`);
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
270
|
+
const pullfrom = [];
|
|
271
|
+
for (const pullUsername of pullfromNames) {
|
|
272
|
+
pullfrom.push({
|
|
273
|
+
username: pullUsername,
|
|
274
|
+
username_hash: await this.hashUsernameForWrite(pullUsername),
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
const title = (typeof existing?.title === "string" && existing.title.trim()) || keyId;
|
|
278
|
+
const comment = typeof existing?.comment === "string" && existing.comment.trim()
|
|
279
|
+
? existing.comment.trim()
|
|
280
|
+
: undefined;
|
|
281
|
+
await this.publishKeyDistribution({
|
|
282
|
+
v: 1,
|
|
283
|
+
keyId,
|
|
284
|
+
keyVersions,
|
|
285
|
+
title,
|
|
286
|
+
...(comment ? { comment } : {}),
|
|
287
|
+
preparedByPublicKey: preparedBy,
|
|
288
|
+
pushto,
|
|
289
|
+
pullfrom,
|
|
290
|
+
}, administrationPrivateKey, administrationPrivateKeyPassword);
|
|
291
|
+
this.logger.info(`autoDistributeKeysToUser: merged "${username}" into distribution for "${keyId}"`);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Key ids that already list `username` in pushto (plus `default`), so a newly
|
|
296
|
+
* added device gets re-wrapped material. Protected ids are excluded.
|
|
297
|
+
*/
|
|
298
|
+
async keyIdsNeedingDeviceWrap(username) {
|
|
299
|
+
const lower = username.trim().toLowerCase();
|
|
300
|
+
const ids = new Set([types_1.DEFAULT_TENANT_KEY_ID]);
|
|
301
|
+
for (const view of await this.listKeyDistributions()) {
|
|
302
|
+
if (view.pushtoUsernames?.some((u) => u.trim().toLowerCase() === lower)) {
|
|
303
|
+
ids.add(view.keyId);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
return [...ids].filter((id) => !types_3.PROTECTED_DISTRIBUTION_KEY_IDS.includes(id));
|
|
219
307
|
}
|
|
220
308
|
async findGrantAccessDocuments(username) {
|
|
221
309
|
this.logger.debug(`Finding grant access documents for username: ${username}`);
|
|
@@ -421,6 +509,7 @@ class BaseMindooTenantDirectory {
|
|
|
421
509
|
}
|
|
422
510
|
// Track group documents by name for merging (handles offline sync scenarios)
|
|
423
511
|
const groupDocsByName = new Map();
|
|
512
|
+
let sawGrantAccessChange = false;
|
|
424
513
|
// Process changes (documents are returned in order by lastModified, oldest to newest)
|
|
425
514
|
for await (const { doc, cursor } of directoryDB.iterateChangesSince(startCursor)) {
|
|
426
515
|
const data = doc.getData();
|
|
@@ -440,6 +529,7 @@ class BaseMindooTenantDirectory {
|
|
|
440
529
|
}
|
|
441
530
|
}
|
|
442
531
|
if (data.type === "grantaccess") {
|
|
532
|
+
sawGrantAccessChange = true;
|
|
443
533
|
// Record this grant document's CURRENT signing keys. Because grant
|
|
444
534
|
// documents are Automerge-merged in place, removing keys (the only
|
|
445
535
|
// revocation mechanism, §6.5) is reflected by a smaller array here.
|
|
@@ -580,6 +670,9 @@ class BaseMindooTenantDirectory {
|
|
|
580
670
|
if (newlyTrustedKeys.size > 0) {
|
|
581
671
|
void this.tenant.reconcileAuthorTrustChangesSafe(newlyTrustedKeys);
|
|
582
672
|
}
|
|
673
|
+
if (sawGrantAccessChange) {
|
|
674
|
+
this.tenant.scheduleUserKeyReconcile?.();
|
|
675
|
+
}
|
|
583
676
|
// Merge group documents with the same name
|
|
584
677
|
for (const [normalizedGroupName, docs] of groupDocsByName.entries()) {
|
|
585
678
|
// Collect all member hashes and encrypted values from all documents with this group name
|
|
@@ -823,6 +916,25 @@ class BaseMindooTenantDirectory {
|
|
|
823
916
|
isServer: false,
|
|
824
917
|
});
|
|
825
918
|
}
|
|
919
|
+
/**
|
|
920
|
+
* Grant-level `$author` resolver: creator at `creatorTrustedTime`, signer at
|
|
921
|
+
* `signerTrustedTime`. See {@link isSamePerson}.
|
|
922
|
+
*/
|
|
923
|
+
async isSamePerson(input) {
|
|
924
|
+
const creatorNode = await this.getDirectoryStateAt(input.creatorTrustedTime);
|
|
925
|
+
const signerNode = input.signerTrustedTime === input.creatorTrustedTime
|
|
926
|
+
? creatorNode
|
|
927
|
+
: await this.getDirectoryStateAt(input.signerTrustedTime);
|
|
928
|
+
return (0, DirectoryStateNode_1.isSamePerson)(creatorNode, signerNode, input.creatorSigningKey, input.signerSigningKey);
|
|
929
|
+
}
|
|
930
|
+
/**
|
|
931
|
+
* The `username_hash` of the active grant that contained `signingKey` at
|
|
932
|
+
* trusted time `T`, or `null` when the key was not a granted device then.
|
|
933
|
+
*/
|
|
934
|
+
async resolveUsernameHashForSigningKey(signingKey, trustedTime) {
|
|
935
|
+
const node = await this.getDirectoryStateAt(trustedTime);
|
|
936
|
+
return (0, DirectoryStateNode_1.grantForSigningKey)(node, signingKey)?.usernameHash ?? null;
|
|
937
|
+
}
|
|
826
938
|
/**
|
|
827
939
|
* Whether the active policy has any Tier 2 (`withfields`) content rule for
|
|
828
940
|
* operation `op` in database `dbid` at the current head state. Lets the
|
|
@@ -927,7 +1039,7 @@ class BaseMindooTenantDirectory {
|
|
|
927
1039
|
* bypass).
|
|
928
1040
|
*/
|
|
929
1041
|
async isDatabaseAllowed(dbid, opts) {
|
|
930
|
-
if (dbid ===
|
|
1042
|
+
if (dbid === types_1.DIRECTORY_DB_ID || dbid === types_1.USER_DIRECTORY_DB_ID)
|
|
931
1043
|
return true;
|
|
932
1044
|
const { mode, allowedDbIds } = await this.getDatabaseCreationPolicy();
|
|
933
1045
|
if (mode === "open")
|
|
@@ -954,7 +1066,7 @@ class BaseMindooTenantDirectory {
|
|
|
954
1066
|
}
|
|
955
1067
|
// The directory database is never read-gated (it must always sync so the
|
|
956
1068
|
// policy that defines the read gate can be read at all).
|
|
957
|
-
if (input.dbid ===
|
|
1069
|
+
if (input.dbid === types_1.DIRECTORY_DB_ID || input.dbid === types_1.USER_DIRECTORY_DB_ID)
|
|
958
1070
|
return true;
|
|
959
1071
|
// The tenant admin is exempt from the read gate.
|
|
960
1072
|
const adminKey = this.tenant.getAdministrationPublicKey();
|
|
@@ -984,7 +1096,7 @@ class BaseMindooTenantDirectory {
|
|
|
984
1096
|
* ({@link isDatabaseAllowed}); callers enforce both gates.
|
|
985
1097
|
*/
|
|
986
1098
|
async canReadDatabase(dbid) {
|
|
987
|
-
if (dbid ===
|
|
1099
|
+
if (dbid === types_1.DIRECTORY_DB_ID || dbid === types_1.USER_DIRECTORY_DB_ID)
|
|
988
1100
|
return true;
|
|
989
1101
|
const currentUser = await this.tenant.getCurrentUserId();
|
|
990
1102
|
const adminKey = this.tenant.getAdministrationPublicKey();
|
|
@@ -1177,16 +1289,61 @@ class BaseMindooTenantDirectory {
|
|
|
1177
1289
|
* Pairs are merged by signing key (a pair with an already-present signing key
|
|
1178
1290
|
* updates its encryption key/label). See {@link mergeKeyPairs}.
|
|
1179
1291
|
*/
|
|
1180
|
-
async addUserKeys(username, keyPairs, administrationPrivateKey, administrationPrivateKeyPassword) {
|
|
1292
|
+
async addUserKeys(username, keyPairs, administrationPrivateKey, administrationPrivateKeyPassword, distributeKeyIds) {
|
|
1293
|
+
const now = (0, timeSource_1.semanticNow)();
|
|
1181
1294
|
const additions = keyPairs.map((pair) => {
|
|
1182
1295
|
const trimmedLabel = typeof pair.label === "string" ? pair.label.trim() : "";
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1296
|
+
const next = {
|
|
1297
|
+
signingPublicKey: pair.signingPublicKey,
|
|
1298
|
+
encryptionPublicKey: pair.encryptionPublicKey,
|
|
1299
|
+
addedAt: pair.addedAt ?? now,
|
|
1300
|
+
};
|
|
1301
|
+
if (trimmedLabel.length > 0)
|
|
1302
|
+
next.label = trimmedLabel;
|
|
1303
|
+
if (pair.revoked) {
|
|
1304
|
+
next.revoked = true;
|
|
1305
|
+
if (typeof pair.revokedAt === "number")
|
|
1306
|
+
next.revokedAt = pair.revokedAt;
|
|
1307
|
+
}
|
|
1308
|
+
const wrapped = typeof pair.joinResponseWrapped === "string" ? pair.joinResponseWrapped.trim() : "";
|
|
1309
|
+
if (wrapped)
|
|
1310
|
+
next.joinResponseWrapped = wrapped;
|
|
1311
|
+
return next;
|
|
1186
1312
|
});
|
|
1187
1313
|
await this.editGrantArrays(username, administrationPrivateKey, administrationPrivateKeyPassword, (data) => {
|
|
1188
1314
|
(0, grantKeys_1.applyKeyPairFields)(data, (0, grantKeys_1.mergeKeyPairs)((0, grantKeys_1.extractKeyPairs)(data), additions));
|
|
1189
1315
|
});
|
|
1316
|
+
// Device enrollment no longer re-wraps named keys: wraps target User-Keys,
|
|
1317
|
+
// which do not change when a device is added. An existing approved device
|
|
1318
|
+
// wraps the User-Key for the new device instead.
|
|
1319
|
+
if (distributeKeyIds !== undefined && distributeKeyIds.length > 0) {
|
|
1320
|
+
await this.autoDistributeKeysToUser(username, distributeKeyIds, administrationPrivateKey, administrationPrivateKeyPassword);
|
|
1321
|
+
}
|
|
1322
|
+
}
|
|
1323
|
+
/**
|
|
1324
|
+
* Store a per-device RSA-hybrid join bootstrap (`username` + optional
|
|
1325
|
+
* `tenantLabel`) on the matching `userKeyPairs` entry. The blob is
|
|
1326
|
+
* `$publicinfos`-readable so device discovery can forward it; only the
|
|
1327
|
+
* device's encryption private key can unwrap it.
|
|
1328
|
+
*/
|
|
1329
|
+
async attachDeviceJoinBootstrap(username, signingPublicKey, wrappedJoinResponse, administrationPrivateKey, administrationPrivateKeyPassword) {
|
|
1330
|
+
const wrapped = wrappedJoinResponse.trim();
|
|
1331
|
+
if (!wrapped) {
|
|
1332
|
+
throw new Error("Cannot attach device join bootstrap: wrappedJoinResponse is empty");
|
|
1333
|
+
}
|
|
1334
|
+
let found = false;
|
|
1335
|
+
await this.editGrantArrays(username, administrationPrivateKey, administrationPrivateKeyPassword, (data) => {
|
|
1336
|
+
const next = (0, grantKeys_1.extractKeyPairs)(data).map((pair) => {
|
|
1337
|
+
if (!(0, DirectoryStateNode_1.signingKeysEqual)(pair.signingPublicKey, signingPublicKey))
|
|
1338
|
+
return pair;
|
|
1339
|
+
found = true;
|
|
1340
|
+
return { ...pair, joinResponseWrapped: wrapped };
|
|
1341
|
+
});
|
|
1342
|
+
(0, grantKeys_1.applyKeyPairFields)(data, next);
|
|
1343
|
+
});
|
|
1344
|
+
if (!found) {
|
|
1345
|
+
throw new Error(`Cannot attach device join bootstrap: no granted device matches the signing key for "${username}"`);
|
|
1346
|
+
}
|
|
1190
1347
|
}
|
|
1191
1348
|
/**
|
|
1192
1349
|
* Revoke keys from a user's grant (§6.5 revoke a device/key). A device is
|
|
@@ -1498,6 +1655,61 @@ class BaseMindooTenantDirectory {
|
|
|
1498
1655
|
devices,
|
|
1499
1656
|
};
|
|
1500
1657
|
}
|
|
1658
|
+
/**
|
|
1659
|
+
* Wrap every stored version of `keyId` to the recipient's published User-Key
|
|
1660
|
+
* (one wrap per person, not per device). Throws if the caller does not hold
|
|
1661
|
+
* the key. Returns `null` when the recipient has no published (non-pending)
|
|
1662
|
+
* User-Key — callers treat that as a pending state, not an error.
|
|
1663
|
+
*
|
|
1664
|
+
* `publicKeyOverride` is the join-bootstrap exception: the admin wraps
|
|
1665
|
+
* `default` to the User-Key from the join request before the person has
|
|
1666
|
+
* written a device wrap.
|
|
1667
|
+
*/
|
|
1668
|
+
async wrapKeyForUser(keyId, username, publicKeyOverride) {
|
|
1669
|
+
const baseTenant = this.tenant;
|
|
1670
|
+
const versions = await baseTenant.fingerprintKeyVersions(keyId);
|
|
1671
|
+
if (versions.length === 0) {
|
|
1672
|
+
throw new Error(`Cannot wrap key: key "${keyId}" is not in your KeyBag`);
|
|
1673
|
+
}
|
|
1674
|
+
let publicKey = publicKeyOverride;
|
|
1675
|
+
let fingerprint;
|
|
1676
|
+
if (!publicKey) {
|
|
1677
|
+
const published = await baseTenant.getUserKeyManager().publishedUserKeyFor(username);
|
|
1678
|
+
// Wrap even while the User-Key document is still pending (no device
|
|
1679
|
+
// wraps yet). The public key is already the person's; later devices
|
|
1680
|
+
// unwrap `default` from this document after they import the User-Key.
|
|
1681
|
+
if (!published) {
|
|
1682
|
+
this.logger.warn(`wrapKeyForUser: no published User-Key for "${username}" — cannot wrap "${keyId}"`);
|
|
1683
|
+
return null;
|
|
1684
|
+
}
|
|
1685
|
+
publicKey = published.publicKey;
|
|
1686
|
+
// Derive the map key from the PEM we actually encrypt to, never from the
|
|
1687
|
+
// document's fingerprint field. If the two ever disagree, the recipient
|
|
1688
|
+
// finds a wrap under its own fingerprint that its private key cannot
|
|
1689
|
+
// open — an undebuggable "wrong User-Key or corrupt wrap".
|
|
1690
|
+
const derived = await this.encryptionKeyFingerprint(publicKey);
|
|
1691
|
+
if (published.fingerprint && published.fingerprint !== derived) {
|
|
1692
|
+
this.logger.warn(`wrapKeyForUser: User-Key document for "${username}" is inconsistent — ` +
|
|
1693
|
+
`fingerprint field says ${published.fingerprint} but its publicKey hashes to ${derived}; ` +
|
|
1694
|
+
`wrapping "${keyId}" under ${derived}`);
|
|
1695
|
+
}
|
|
1696
|
+
fingerprint = derived;
|
|
1697
|
+
}
|
|
1698
|
+
if (!fingerprint) {
|
|
1699
|
+
fingerprint = await this.encryptionKeyFingerprint(publicKey);
|
|
1700
|
+
}
|
|
1701
|
+
const rsa = new RSAEncryption_1.RSAEncryption(this.tenant.getCryptoAdapter(), this.logger.createChild("RSAEncryption"));
|
|
1702
|
+
const wrappedByVersion = {};
|
|
1703
|
+
for (const version of versions) {
|
|
1704
|
+
const wrapped = await rsa.encrypt(version.bytes, publicKey);
|
|
1705
|
+
wrappedByVersion[version.fingerprint] = this.bytesToBase64(wrapped);
|
|
1706
|
+
}
|
|
1707
|
+
return {
|
|
1708
|
+
username,
|
|
1709
|
+
username_hash: await this.hashUsernameForWrite(username),
|
|
1710
|
+
devices: { [fingerprint]: wrappedByVersion },
|
|
1711
|
+
};
|
|
1712
|
+
}
|
|
1501
1713
|
/**
|
|
1502
1714
|
* The canonical write-time hash for `username` (the value stored in
|
|
1503
1715
|
* `pushto_users_hashes` / `pullfrom_users_hashes`). Exposed so callers (the
|
|
@@ -1507,6 +1719,13 @@ class BaseMindooTenantDirectory {
|
|
|
1507
1719
|
async getUsernameHash(username) {
|
|
1508
1720
|
return this.hashUsernameForWrite(username);
|
|
1509
1721
|
}
|
|
1722
|
+
/**
|
|
1723
|
+
* All hash forms a given username could be stored as (v1/v2/v3). Public so
|
|
1724
|
+
* User-Key lookup can scan `userdirectory` the same way grants are found.
|
|
1725
|
+
*/
|
|
1726
|
+
async getUsernameHashCandidates(username) {
|
|
1727
|
+
return this.usernameHashCandidates(username);
|
|
1728
|
+
}
|
|
1510
1729
|
/**
|
|
1511
1730
|
* The version manifest (`{createdAt, fingerprint}`) of `keyId` from the
|
|
1512
1731
|
* caller's KeyBag. The single source of truth for the distribution's
|
|
@@ -1547,15 +1766,31 @@ class BaseMindooTenantDirectory {
|
|
|
1547
1766
|
pullfrom_users_hashes: pullfromHashes,
|
|
1548
1767
|
pushto_users_keys: pushtoUsersKeys,
|
|
1549
1768
|
});
|
|
1550
|
-
// Directory-level validation: every pushto user must have an active grant
|
|
1551
|
-
//
|
|
1552
|
-
//
|
|
1769
|
+
// Directory-level validation: every pushto user must have an active grant.
|
|
1770
|
+
// Coverage is per published User-Key epoch (or per device for service
|
|
1771
|
+
// accounts that have no User-Key).
|
|
1553
1772
|
const manifestFps = new Set(request.keyVersions.map((v) => v.fingerprint));
|
|
1554
1773
|
for (const p of request.pushto) {
|
|
1555
1774
|
const userKeys = await this.getUserPublicKeys(p.username);
|
|
1556
1775
|
if (!userKeys) {
|
|
1557
1776
|
throw new Error(`Cannot publish: pushto user "${p.username}" has no active grant`);
|
|
1558
1777
|
}
|
|
1778
|
+
const baseTenant = this.tenant;
|
|
1779
|
+
const published = await baseTenant.getUserKeyManager().publishedUserKeyFor(p.username);
|
|
1780
|
+
if (published) {
|
|
1781
|
+
// Pending documents are the join-bootstrap case: the admin wraps
|
|
1782
|
+
// `default` to the join-request User-Key before the person has written
|
|
1783
|
+
// a device wrap. Coverage is still per User-Key fingerprint.
|
|
1784
|
+
const wrapped = p.devices[published.fingerprint];
|
|
1785
|
+
if (!wrapped) {
|
|
1786
|
+
throw new Error(`Cannot publish: pushto user "${p.username}" has no wrap for their User-Key`);
|
|
1787
|
+
}
|
|
1788
|
+
const covered = Object.keys(wrapped);
|
|
1789
|
+
if (covered.length !== manifestFps.size || covered.some((fp) => !manifestFps.has(fp))) {
|
|
1790
|
+
throw new Error(`Cannot publish: User-Key coverage for "${p.username}" does not match the version manifest`);
|
|
1791
|
+
}
|
|
1792
|
+
continue;
|
|
1793
|
+
}
|
|
1559
1794
|
const activePems = await this.getUserEncryptionPublicKeys(p.username);
|
|
1560
1795
|
for (const pem of activePems) {
|
|
1561
1796
|
const deviceFp = await this.encryptionKeyFingerprint(pem);
|
|
@@ -1722,13 +1957,41 @@ class BaseMindooTenantDirectory {
|
|
|
1722
1957
|
*/
|
|
1723
1958
|
async reconcileImportedKeysForCurrentUser(username) {
|
|
1724
1959
|
const baseTenant = this.tenant;
|
|
1725
|
-
const
|
|
1726
|
-
|
|
1960
|
+
const keys = [];
|
|
1961
|
+
const userKeys = await baseTenant.getUserKeyManager().getUserKeyCryptoKeysForReconcile();
|
|
1962
|
+
keys.push(...userKeys);
|
|
1963
|
+
const deviceKey = await baseTenant.getEncryptionPrivateKeyForReconcile();
|
|
1964
|
+
if (deviceKey)
|
|
1965
|
+
keys.push(deviceKey);
|
|
1966
|
+
if (keys.length === 0) {
|
|
1727
1967
|
this.logger.debug("reconcileImportedKeysForCurrentUser: no encryption key available; " +
|
|
1728
1968
|
"skipping import pass (revoke pass still ran)");
|
|
1729
1969
|
return { imported: [], removed: [] };
|
|
1730
1970
|
}
|
|
1731
|
-
|
|
1971
|
+
let imported = [];
|
|
1972
|
+
let removed = [];
|
|
1973
|
+
// A wrap is addressed to exactly one of these keys, so every other candidate
|
|
1974
|
+
// fails on it by construction. Collect the misses and judge them only once
|
|
1975
|
+
// the whole pass is done, otherwise the losing candidates cry wolf on a key
|
|
1976
|
+
// that was delivered fine.
|
|
1977
|
+
const unopened = new Map();
|
|
1978
|
+
for (const key of keys) {
|
|
1979
|
+
const result = await this.reconcileKeyDistributionsWithKey(username, key);
|
|
1980
|
+
imported = Array.from(new Set([...imported, ...result.imported]));
|
|
1981
|
+
removed = Array.from(new Set([...removed, ...result.removed]));
|
|
1982
|
+
for (const [keyId, reason] of result.unopened ?? []) {
|
|
1983
|
+
unopened.set(keyId, [...(unopened.get(keyId) ?? []), reason]);
|
|
1984
|
+
}
|
|
1985
|
+
}
|
|
1986
|
+
for (const [keyId, reasons] of unopened) {
|
|
1987
|
+
if (imported.includes(keyId))
|
|
1988
|
+
continue;
|
|
1989
|
+
if (await baseTenant.hasDecryptionKey(keyId))
|
|
1990
|
+
continue;
|
|
1991
|
+
this.logger.warn(`reconcileKeyDistributions: ${keyId} not unwrapped by any of the ${keys.length} ` +
|
|
1992
|
+
`available encryption key(s) (wrong User-Key or corrupt wrap): ${reasons.join(" || ")}`);
|
|
1993
|
+
}
|
|
1994
|
+
return { imported, removed };
|
|
1732
1995
|
}
|
|
1733
1996
|
/**
|
|
1734
1997
|
* Reconcile the local KeyBag against the directory head for `username`
|
|
@@ -1755,14 +2018,35 @@ class BaseMindooTenantDirectory {
|
|
|
1755
2018
|
const directoryDB = await this.getDirectoryDB();
|
|
1756
2019
|
await directoryDB.syncStoreChanges();
|
|
1757
2020
|
await this.updateUnifiedCache();
|
|
1758
|
-
if (this.keyDistributionCache.size === 0)
|
|
2021
|
+
if (this.keyDistributionCache.size === 0) {
|
|
2022
|
+
this.logger.warn("reconcileKeyDistributionsWithKey: directory has no key-distribution documents in cache");
|
|
1759
2023
|
return { imported: [], removed: [] };
|
|
2024
|
+
}
|
|
2025
|
+
if (!this.keyDistributionCache.has(types_1.DEFAULT_TENANT_KEY_ID)) {
|
|
2026
|
+
this.logger.warn("reconcileKeyDistributionsWithKey: no acl_keydistribution_default in directory cache");
|
|
2027
|
+
}
|
|
1760
2028
|
const myHashes = new Set(await this.usernameHashCandidates(username));
|
|
2029
|
+
// Nameless / pre-adoption devices: also match via the grant's
|
|
2030
|
+
// identity_hashes / username_hash looked up by signing public key.
|
|
2031
|
+
try {
|
|
2032
|
+
const signingKey = (await this.tenant.getCurrentUserId()).userSigningPublicKey;
|
|
2033
|
+
const lookup = await this.getUserBySigningPublicKey(signingKey);
|
|
2034
|
+
if (lookup) {
|
|
2035
|
+
for (const h of await this.identityHashesForLookup(lookup)) {
|
|
2036
|
+
myHashes.add(h);
|
|
2037
|
+
}
|
|
2038
|
+
}
|
|
2039
|
+
}
|
|
2040
|
+
catch (error) {
|
|
2041
|
+
this.logger.debug(`reconcileKeyDistributionsWithKey: signing-key hash supplement failed: ${error}`);
|
|
2042
|
+
}
|
|
1761
2043
|
const baseTenant = this.tenant;
|
|
1762
2044
|
const privateKey = encryptionPrivateCryptoKey;
|
|
1763
2045
|
const rsa = new RSAEncryption_1.RSAEncryption(this.tenant.getCryptoAdapter(), this.logger.createChild("RSAEncryption"));
|
|
1764
2046
|
const imported = [];
|
|
1765
2047
|
const removed = [];
|
|
2048
|
+
// keyId -> why this candidate key could not open any of its wraps.
|
|
2049
|
+
const unopened = new Map();
|
|
1766
2050
|
for (const [keyId, entry] of this.keyDistributionCache.entries()) {
|
|
1767
2051
|
if (types_3.PROTECTED_DISTRIBUTION_KEY_IDS.includes(keyId))
|
|
1768
2052
|
continue;
|
|
@@ -1778,8 +2062,13 @@ class BaseMindooTenantDirectory {
|
|
|
1778
2062
|
removed.push(keyId);
|
|
1779
2063
|
continue;
|
|
1780
2064
|
}
|
|
1781
|
-
if (!meInPush)
|
|
2065
|
+
if (!meInPush) {
|
|
2066
|
+
if (keyId === types_1.DEFAULT_TENANT_KEY_ID) {
|
|
2067
|
+
this.logger.warn(`reconcileKeyDistributions: "${keyId}" exists but current user is not in pushto ` +
|
|
2068
|
+
`(hashes=${entry.pushto_users_hashes.length})`);
|
|
2069
|
+
}
|
|
1782
2070
|
continue;
|
|
2071
|
+
}
|
|
1783
2072
|
const wrappedMap = entry.pushto_users_keys;
|
|
1784
2073
|
// Collect device entries addressed to me (by hash prefix). We do not know
|
|
1785
2074
|
// which device is ours without trying, so attempt to unwrap each; only our
|
|
@@ -1788,39 +2077,63 @@ class BaseMindooTenantDirectory {
|
|
|
1788
2077
|
const hash = entryKey.split("|")[0];
|
|
1789
2078
|
return myHashes.has(hash);
|
|
1790
2079
|
});
|
|
1791
|
-
if (myEntries.length === 0)
|
|
2080
|
+
if (myEntries.length === 0) {
|
|
2081
|
+
this.logger.warn(`reconcileKeyDistributions: "${keyId}" lists this user in pushto but has no wrap entries for their hashes`);
|
|
1792
2082
|
continue;
|
|
2083
|
+
}
|
|
1793
2084
|
const collected = new Map();
|
|
1794
|
-
|
|
2085
|
+
// Why each candidate failed, so a "none unwrapped" warning can name the
|
|
2086
|
+
// cause instead of lumping a missing version in with a decrypt failure.
|
|
2087
|
+
const attempts = [];
|
|
2088
|
+
for (const [entryKey, wrappedByVersion] of myEntries) {
|
|
1795
2089
|
for (const ref of manifest) {
|
|
1796
2090
|
if (collected.has(ref.fingerprint))
|
|
1797
2091
|
continue;
|
|
1798
2092
|
const wrappedB64 = wrappedByVersion[ref.fingerprint];
|
|
1799
|
-
if (typeof wrappedB64 !== "string")
|
|
2093
|
+
if (typeof wrappedB64 !== "string") {
|
|
2094
|
+
attempts.push(`${entryKey}: no wrap for version ${ref.fingerprint}; entry carries [${Object.keys(wrappedByVersion).join(", ")}]`);
|
|
1800
2095
|
continue;
|
|
2096
|
+
}
|
|
1801
2097
|
try {
|
|
1802
2098
|
const bytes = await rsa.decrypt(this.base64ToBytes(wrappedB64), privateKey);
|
|
1803
2099
|
const actualFp = await baseTenant.fingerprintKeyBytes(bytes);
|
|
1804
2100
|
if (actualFp !== ref.fingerprint) {
|
|
1805
2101
|
this.logger.warn(`reconcileKeyDistributions: fingerprint mismatch for ${keyId}@${ref.fingerprint} (got ${actualFp}); rejecting`);
|
|
2102
|
+
attempts.push(`${entryKey}: unwrapped ${ref.fingerprint} but got key ${actualFp}`);
|
|
1806
2103
|
continue;
|
|
1807
2104
|
}
|
|
1808
2105
|
collected.set(ref.fingerprint, { bytes, keyVersionCreatedAt: ref.createdAt });
|
|
1809
2106
|
}
|
|
1810
|
-
catch {
|
|
1811
|
-
//
|
|
2107
|
+
catch (error) {
|
|
2108
|
+
// Byte length separates a truncated/corrupt wrap from a genuine
|
|
2109
|
+
// key mismatch: hybrid format is 2 + 384 (RSA-3072) + 12 + AES.
|
|
2110
|
+
const wrappedBytes = this.base64ToBytes(wrappedB64);
|
|
2111
|
+
const rsaBlockLength = (wrappedBytes[0] << 8) | wrappedBytes[1];
|
|
2112
|
+
attempts.push(`${entryKey}: RSA decrypt of version ${ref.fingerprint} failed ` +
|
|
2113
|
+
`(${error instanceof Error ? error.message || error.name : error}); ` +
|
|
2114
|
+
`wrap b64=${wrappedB64.length} bytes=${wrappedBytes.length} rsaBlock=${rsaBlockLength}`);
|
|
1812
2115
|
}
|
|
1813
2116
|
}
|
|
1814
2117
|
if (collected.size === manifest.length)
|
|
1815
2118
|
break;
|
|
1816
2119
|
}
|
|
1817
|
-
if (collected.size === 0)
|
|
2120
|
+
if (collected.size === 0) {
|
|
2121
|
+
// Not an error yet: the caller runs this pass once per candidate key
|
|
2122
|
+
// (every User-Key epoch, then the device key), so a wrap addressed to
|
|
2123
|
+
// the User-Key can never open with the device key. Only the caller knows
|
|
2124
|
+
// whether some other candidate succeeded, so report instead of warning.
|
|
2125
|
+
const addressedTo = myEntries.map(([entryKey]) => entryKey.split("|")[1] ?? "?");
|
|
2126
|
+
unopened.set(keyId, `${myEntries.length} wrap(s) for this user, none unwrapped; ` +
|
|
2127
|
+
`wraps addressed to [${addressedTo.join(", ")}], ` +
|
|
2128
|
+
`manifest versions [${manifest.map((ref) => ref.fingerprint).join(", ")}]; ` +
|
|
2129
|
+
`attempts: ${attempts.join(" | ")}`);
|
|
1818
2130
|
continue;
|
|
2131
|
+
}
|
|
1819
2132
|
const count = await baseTenant.importDeliveredDecryptionKeyVersions(keyId, Array.from(collected.values()));
|
|
1820
2133
|
if (count > 0)
|
|
1821
2134
|
imported.push(keyId);
|
|
1822
2135
|
}
|
|
1823
|
-
return { imported, removed };
|
|
2136
|
+
return { imported, removed, unopened };
|
|
1824
2137
|
}
|
|
1825
2138
|
/**
|
|
1826
2139
|
* Whether `keyId` is managed by a distribution at the directory head AND the
|
|
@@ -2345,13 +2658,13 @@ class BaseMindooTenantDirectory {
|
|
|
2345
2658
|
return hashes;
|
|
2346
2659
|
}
|
|
2347
2660
|
/**
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2661
|
+
* Hash-space core of the revoked-key blacklist (docs/accesscontrol.md §13):
|
|
2662
|
+
* every `acl_keydistribution_<keyId>` whose `pullfrom_users_hashes` intersects
|
|
2663
|
+
* `myHashes`. Protected id `$publicinfos` is never reported. Reads straight from
|
|
2664
|
+
* {@link keyDistributionCache} (O(cache size), no doc scan); the caller must have
|
|
2665
|
+
* refreshed it first (both entry points do, via `updateUnifiedCache` /
|
|
2666
|
+
* `getUserBySigningPublicKey`).
|
|
2667
|
+
*/
|
|
2355
2668
|
collectRevokedKeyIdsForHashes(myHashes) {
|
|
2356
2669
|
const revoked = [];
|
|
2357
2670
|
for (const [keyId, entry] of this.keyDistributionCache.entries()) {
|
|
@@ -2439,7 +2752,7 @@ class BaseMindooTenantDirectory {
|
|
|
2439
2752
|
// allow-rules until the trust cache refreshed (up to
|
|
2440
2753
|
// DIRECTORY_SYNC_INTERVAL_MS later). The `node` is the time-`T` snapshot
|
|
2441
2754
|
// whose `bySigningKey` only contains keys whose grant is active at `T`.
|
|
2442
|
-
const grantedAtT =
|
|
2755
|
+
const grantedAtT = !!(0, DirectoryStateNode_1.grantForSigningKey)(node, signingKey);
|
|
2443
2756
|
// Resolve the author's username from their signing key. Trusted non-user
|
|
2444
2757
|
// keys (e.g. server-to-server sync identities) have no grant; treat them as
|
|
2445
2758
|
// an empty identity that still carries `$everyone`/`$author`. Keys that were
|
|
@@ -2650,7 +2963,19 @@ class BaseMindooTenantDirectory {
|
|
|
2650
2963
|
const directoryDB = await this.getDirectoryDB();
|
|
2651
2964
|
await directoryDB.syncStoreChanges();
|
|
2652
2965
|
await this.updateUnifiedCache();
|
|
2653
|
-
|
|
2966
|
+
const exact = this.userLookupCache.get(publicKey);
|
|
2967
|
+
if (exact)
|
|
2968
|
+
return exact;
|
|
2969
|
+
// Auth/JWT/client transport may re-wrap PEM line endings; match the grant
|
|
2970
|
+
// entry whose signing key is the same key material.
|
|
2971
|
+
const needle = publicKey.replace(/\s+/g, "");
|
|
2972
|
+
if (!needle)
|
|
2973
|
+
return null;
|
|
2974
|
+
for (const [key, lookup] of this.userLookupCache) {
|
|
2975
|
+
if (key.replace(/\s+/g, "") === needle)
|
|
2976
|
+
return lookup;
|
|
2977
|
+
}
|
|
2978
|
+
return null;
|
|
2654
2979
|
}
|
|
2655
2980
|
/**
|
|
2656
2981
|
* Resolve the full set of signing keys relevant to authenticating a device for
|
|
@@ -2881,7 +3206,7 @@ class BaseMindooTenantDirectory {
|
|
|
2881
3206
|
const directoryDB = await this.getDirectoryDB();
|
|
2882
3207
|
await directoryDB.syncStoreChanges();
|
|
2883
3208
|
await this.updateUnifiedCache();
|
|
2884
|
-
const known = new Set([
|
|
3209
|
+
const known = new Set([types_1.DIRECTORY_DB_ID, types_1.USER_DIRECTORY_DB_ID]);
|
|
2885
3210
|
for (const dbId of this.dbSettingsCache.keys()) {
|
|
2886
3211
|
known.add(dbId);
|
|
2887
3212
|
}
|
|
@@ -3282,6 +3607,9 @@ class BaseMindooTenantDirectory {
|
|
|
3282
3607
|
details,
|
|
3283
3608
|
identityHashes,
|
|
3284
3609
|
identityHashesV,
|
|
3610
|
+
...(pair.joinResponseWrapped
|
|
3611
|
+
? { joinResponseWrapped: pair.joinResponseWrapped }
|
|
3612
|
+
: {}),
|
|
3285
3613
|
}));
|
|
3286
3614
|
}
|
|
3287
3615
|
async encryptGroupMemberForTenant(plaintext) {
|