mindoodb 0.0.94 → 0.0.96
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/dist/appendonlystores/network/ClientNetworkContentAddressedStore.d.ts +17 -1
- package/dist/appendonlystores/network/ClientNetworkContentAddressedStore.d.ts.map +1 -1
- package/dist/appendonlystores/network/ClientNetworkContentAddressedStore.js +63 -4
- package/dist/appendonlystores/network/ClientNetworkContentAddressedStore.js.map +1 -1
- package/dist/appendonlystores/network/HttpTransport.d.ts +10 -1
- package/dist/appendonlystores/network/HttpTransport.d.ts.map +1 -1
- package/dist/appendonlystores/network/HttpTransport.js +45 -3
- package/dist/appendonlystores/network/HttpTransport.js.map +1 -1
- package/dist/appendonlystores/network/ServerNetworkContentAddressedStore.d.ts +22 -0
- package/dist/appendonlystores/network/ServerNetworkContentAddressedStore.d.ts.map +1 -1
- package/dist/appendonlystores/network/ServerNetworkContentAddressedStore.js +33 -0
- package/dist/appendonlystores/network/ServerNetworkContentAddressedStore.js.map +1 -1
- package/dist/browser/index.mjs +42 -0
- package/dist/core/BaseMindooDB.d.ts +9 -0
- package/dist/core/BaseMindooDB.d.ts.map +1 -1
- package/dist/core/BaseMindooDB.js +75 -16
- package/dist/core/BaseMindooDB.js.map +1 -1
- package/dist/core/BaseMindooTenant.d.ts +82 -1
- package/dist/core/BaseMindooTenant.d.ts.map +1 -1
- package/dist/core/BaseMindooTenant.js +263 -7
- package/dist/core/BaseMindooTenant.js.map +1 -1
- package/dist/core/BaseMindooTenantDirectory.d.ts +182 -2
- package/dist/core/BaseMindooTenantDirectory.d.ts.map +1 -1
- package/dist/core/BaseMindooTenantDirectory.js +683 -113
- package/dist/core/BaseMindooTenantDirectory.js.map +1 -1
- package/dist/core/BaseMindooTenantFactory.d.ts +30 -0
- package/dist/core/BaseMindooTenantFactory.d.ts.map +1 -1
- package/dist/core/BaseMindooTenantFactory.js +95 -0
- package/dist/core/BaseMindooTenantFactory.js.map +1 -1
- package/dist/core/accesscontrol/DirectoryStateNode.d.ts +45 -0
- package/dist/core/accesscontrol/DirectoryStateNode.d.ts.map +1 -1
- package/dist/core/accesscontrol/DirectoryStateNode.js +58 -0
- package/dist/core/accesscontrol/DirectoryStateNode.js.map +1 -1
- package/dist/core/accesscontrol/adminKeys.d.ts +130 -0
- package/dist/core/accesscontrol/adminKeys.d.ts.map +1 -0
- package/dist/core/accesscontrol/adminKeys.js +164 -0
- package/dist/core/accesscontrol/adminKeys.js.map +1 -0
- package/dist/core/accesscontrol/directoryAdminResolver.d.ts +54 -0
- package/dist/core/accesscontrol/directoryAdminResolver.d.ts.map +1 -0
- package/dist/core/accesscontrol/directoryAdminResolver.js +217 -0
- package/dist/core/accesscontrol/directoryAdminResolver.js.map +1 -0
- package/dist/core/accesscontrol/directoryProjection.d.ts.map +1 -1
- package/dist/core/accesscontrol/directoryProjection.js +25 -0
- package/dist/core/accesscontrol/directoryProjection.js.map +1 -1
- package/dist/core/accesscontrol/grantKeys.d.ts +16 -0
- package/dist/core/accesscontrol/grantKeys.d.ts.map +1 -1
- package/dist/core/accesscontrol/grantKeys.js +37 -0
- package/dist/core/accesscontrol/grantKeys.js.map +1 -1
- package/dist/core/accesscontrol/types.d.ts +11 -2
- package/dist/core/accesscontrol/types.d.ts.map +1 -1
- package/dist/core/accesscontrol/types.js +7 -0
- 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 +33 -0
- package/dist/core/appendonlystores/network/AuthenticationService.js.map +1 -1
- package/dist/core/appendonlystores/network/NetworkTransport.d.ts +16 -1
- package/dist/core/appendonlystores/network/NetworkTransport.d.ts.map +1 -1
- package/dist/core/appendonlystores/network/types.d.ts +13 -0
- package/dist/core/appendonlystores/network/types.d.ts.map +1 -1
- package/dist/core/appendonlystores/network/types.js.map +1 -1
- package/dist/core/builtinDbInvariants.d.ts +18 -2
- package/dist/core/builtinDbInvariants.d.ts.map +1 -1
- package/dist/core/builtinDbInvariants.js +41 -5
- package/dist/core/builtinDbInvariants.js.map +1 -1
- package/dist/core/errors.d.ts +23 -0
- package/dist/core/errors.d.ts.map +1 -1
- package/dist/core/errors.js +34 -1
- package/dist/core/errors.js.map +1 -1
- package/dist/core/index.d.ts +7 -4
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +31 -5
- package/dist/core/index.js.map +1 -1
- package/dist/core/indexing/createViewDataProvider.d.ts +10 -1
- package/dist/core/indexing/createViewDataProvider.d.ts.map +1 -1
- package/dist/core/indexing/createViewDataProvider.js +11 -0
- package/dist/core/indexing/createViewDataProvider.js.map +1 -1
- package/dist/core/indexing/summary/DocumentSummaryStore.d.ts +13 -0
- package/dist/core/indexing/summary/DocumentSummaryStore.d.ts.map +1 -1
- package/dist/core/indexing/summary/DocumentSummaryStore.js +46 -5
- package/dist/core/indexing/summary/DocumentSummaryStore.js.map +1 -1
- package/dist/core/indexing/summary/SummaryVirtualViewDataProvider.d.ts +13 -0
- package/dist/core/indexing/summary/SummaryVirtualViewDataProvider.d.ts.map +1 -1
- package/dist/core/indexing/summary/SummaryVirtualViewDataProvider.js +21 -0
- package/dist/core/indexing/summary/SummaryVirtualViewDataProvider.js.map +1 -1
- package/dist/core/indexing/summary/index.d.ts +1 -1
- package/dist/core/indexing/summary/index.d.ts.map +1 -1
- package/dist/core/indexing/summary/index.js +2 -1
- package/dist/core/indexing/summary/index.js.map +1 -1
- package/dist/core/indexing/summary/types.d.ts +28 -0
- package/dist/core/indexing/summary/types.d.ts.map +1 -1
- package/dist/core/indexing/summary/types.js +22 -0
- package/dist/core/indexing/summary/types.js.map +1 -1
- package/dist/core/indexing/virtualviews/MindooDBVirtualViewDataProvider.d.ts +19 -0
- package/dist/core/indexing/virtualviews/MindooDBVirtualViewDataProvider.d.ts.map +1 -1
- package/dist/core/indexing/virtualviews/MindooDBVirtualViewDataProvider.js +34 -3
- package/dist/core/indexing/virtualviews/MindooDBVirtualViewDataProvider.js.map +1 -1
- package/dist/core/indexing/virtualviews/VirtualViewFactory.d.ts +7 -0
- package/dist/core/indexing/virtualviews/VirtualViewFactory.d.ts.map +1 -1
- package/dist/core/indexing/virtualviews/VirtualViewFactory.js +1 -0
- package/dist/core/indexing/virtualviews/VirtualViewFactory.js.map +1 -1
- package/dist/core/live/LiveChannel.d.ts +41 -0
- package/dist/core/live/LiveChannel.d.ts.map +1 -0
- package/dist/core/live/LiveChannel.js +151 -0
- package/dist/core/live/LiveChannel.js.map +1 -0
- package/dist/core/query/executeQuery.d.ts.map +1 -1
- package/dist/core/query/executeQuery.js +30 -17
- package/dist/core/query/executeQuery.js.map +1 -1
- package/dist/core/query/queryView.d.ts +14 -0
- package/dist/core/query/queryView.d.ts.map +1 -1
- package/dist/core/query/queryView.js +22 -12
- package/dist/core/query/queryView.js.map +1 -1
- package/dist/core/query/types.d.ts +9 -0
- package/dist/core/query/types.d.ts.map +1 -1
- package/dist/core/query/types.js.map +1 -1
- package/dist/core/types.d.ts +193 -6
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/core/uri/MindooURI.d.ts +1 -1
- package/dist/core/uri/MindooURI.d.ts.map +1 -1
- package/dist/core/uri/MindooURI.js +2 -0
- package/dist/core/uri/MindooURI.js.map +1 -1
- package/dist/core/uri/adminUri.d.ts +52 -0
- package/dist/core/uri/adminUri.d.ts.map +1 -0
- package/dist/core/uri/adminUri.js +100 -0
- package/dist/core/uri/adminUri.js.map +1 -0
- package/dist/core/uri/joinRequestUri.d.ts.map +1 -1
- package/dist/core/uri/joinRequestUri.js +14 -1
- package/dist/core/uri/joinRequestUri.js.map +1 -1
- package/dist/core/userid/index.d.ts +9 -0
- package/dist/core/userid/index.d.ts.map +1 -1
- package/dist/core/userkeys/UserKeyDocument.d.ts +7 -1
- package/dist/core/userkeys/UserKeyDocument.d.ts.map +1 -1
- package/dist/core/userkeys/UserKeyDocument.js +14 -3
- package/dist/core/userkeys/UserKeyDocument.js.map +1 -1
- package/dist/core/userkeys/UserKeyManager.d.ts +95 -4
- package/dist/core/userkeys/UserKeyManager.d.ts.map +1 -1
- package/dist/core/userkeys/UserKeyManager.js +606 -59
- package/dist/core/userkeys/UserKeyManager.js.map +1 -1
- package/dist/core/userkeys/deviceCertificate.d.ts +46 -0
- package/dist/core/userkeys/deviceCertificate.d.ts.map +1 -0
- package/dist/core/userkeys/deviceCertificate.js +86 -0
- package/dist/core/userkeys/deviceCertificate.js.map +1 -0
- package/dist/core/userkeys/generationSignature.d.ts +32 -0
- package/dist/core/userkeys/generationSignature.d.ts.map +1 -0
- package/dist/core/userkeys/generationSignature.js +136 -0
- package/dist/core/userkeys/generationSignature.js.map +1 -0
- package/dist/core/userkeys/generationValidity.d.ts +75 -0
- package/dist/core/userkeys/generationValidity.d.ts.map +1 -0
- package/dist/core/userkeys/generationValidity.js +175 -0
- package/dist/core/userkeys/generationValidity.js.map +1 -0
- package/dist/core/userkeys/index.d.ts +5 -2
- package/dist/core/userkeys/index.d.ts.map +1 -1
- package/dist/core/userkeys/index.js +21 -1
- package/dist/core/userkeys/index.js.map +1 -1
- package/dist/core/userkeys/resolveUserKeyDocument.d.ts +16 -0
- package/dist/core/userkeys/resolveUserKeyDocument.d.ts.map +1 -1
- package/dist/core/userkeys/resolveUserKeyDocument.js +42 -1
- package/dist/core/userkeys/resolveUserKeyDocument.js.map +1 -1
- package/dist/core/userkeys/types.d.ts +26 -0
- package/dist/core/userkeys/types.d.ts.map +1 -1
- package/dist/core/userkeys/types.js +3 -1
- package/dist/core/userkeys/types.js.map +1 -1
- package/dist/core/userkeys/validateUserKeyDocument.d.ts.map +1 -1
- package/dist/core/userkeys/validateUserKeyDocument.js +7 -0
- package/dist/core/userkeys/validateUserKeyDocument.js.map +1 -1
- package/dist/core/utils/idGeneration.d.ts +15 -0
- package/dist/core/utils/idGeneration.d.ts.map +1 -1
- package/dist/core/utils/idGeneration.js +45 -0
- package/dist/core/utils/idGeneration.js.map +1 -1
- package/dist/core/utils/index.d.ts +1 -1
- package/dist/core/utils/index.d.ts.map +1 -1
- package/dist/core/utils/index.js +3 -1
- package/dist/core/utils/index.js.map +1 -1
- package/dist/node/server/MindooDBServer.d.ts.map +1 -1
- package/dist/node/server/MindooDBServer.js +11 -0
- package/dist/node/server/MindooDBServer.js.map +1 -1
- package/dist/server/SyncEventBus.d.ts +22 -0
- package/dist/server/SyncEventBus.d.ts.map +1 -1
- package/dist/server/SyncEventBus.js +21 -0
- package/dist/server/SyncEventBus.js.map +1 -1
- package/dist/server/TenantManager.d.ts +9 -0
- package/dist/server/TenantManager.d.ts.map +1 -1
- package/dist/server/TenantManager.js +101 -1
- package/dist/server/TenantManager.js.map +1 -1
- package/dist/server/http/MindooDBServerCore.d.ts +39 -1
- package/dist/server/http/MindooDBServerCore.d.ts.map +1 -1
- package/dist/server/http/MindooDBServerCore.js +121 -2
- package/dist/server/http/MindooDBServerCore.js.map +1 -1
- package/dist/server/http/types.d.ts +10 -1
- package/dist/server/http/types.d.ts.map +1 -1
- package/dist/server/http/types.js.map +1 -1
- package/dist/server/index.d.ts +2 -2
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +2 -1
- package/dist/server/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -3,6 +3,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.BaseMindooTenantDirectory = void 0;
|
|
4
4
|
const types_1 = require("./types");
|
|
5
5
|
const errors_1 = require("./errors");
|
|
6
|
+
const storeEntryTime_1 = require("./storeEntryTime");
|
|
7
|
+
const fingerprint_1 = require("./userkeys/fingerprint");
|
|
8
|
+
const deviceCertificate_1 = require("./userkeys/deviceCertificate");
|
|
6
9
|
const timeSource_1 = require("./utils/timeSource");
|
|
7
10
|
const singleFlight_1 = require("./utils/singleFlight");
|
|
8
11
|
const logging_1 = require("./logging");
|
|
@@ -16,6 +19,8 @@ const types_3 = require("./accesscontrol/types");
|
|
|
16
19
|
const evaluate_1 = require("./accesscontrol/evaluate");
|
|
17
20
|
const RSAEncryption_1 = require("./crypto/RSAEncryption");
|
|
18
21
|
const encryptedFields_1 = require("./crypto/encryptedFields");
|
|
22
|
+
const tenantSetup_1 = require("./tenantSetup");
|
|
23
|
+
const adminKeys_1 = require("./accesscontrol/adminKeys");
|
|
19
24
|
const DIRECTORY_SYNC_INTERVAL_MS = 5 * 60 * 1000;
|
|
20
25
|
const MAX_UNKNOWN_KEYS_TRACKED = 1000;
|
|
21
26
|
/** De-duplicated union of two string lists, preserving first-seen order. */
|
|
@@ -87,6 +92,10 @@ class BaseMindooTenantDirectory {
|
|
|
87
92
|
// Best-effort reverse lookup cache for public signing key -> user details.
|
|
88
93
|
// Entries remain available even after revocation so historical UIs can still show identity labels.
|
|
89
94
|
this.userLookupCache = new Map();
|
|
95
|
+
// Device certificates read from `userdirectory` (docs/userkeys.md §9.6),
|
|
96
|
+
// keyed by the certified device's normalized signing key.
|
|
97
|
+
this.deviceCertIndex = null;
|
|
98
|
+
this.buildingDeviceCertIndex = null;
|
|
90
99
|
// Remote-wipe directive index (docs/accesscontrol.md §6.5): signing public key
|
|
91
100
|
// targeted for wipe -> id of the admin-signed grant document carrying the
|
|
92
101
|
// directive. Self-contained key values survive key removal, so this stays
|
|
@@ -242,10 +251,7 @@ class BaseMindooTenantDirectory {
|
|
|
242
251
|
// Cast to BaseMindooTenant to access protected methods
|
|
243
252
|
const baseTenant = this.tenant;
|
|
244
253
|
// Create SigningKeyPair for the administration key
|
|
245
|
-
const adminSigningKeyPair =
|
|
246
|
-
publicKey: baseTenant.getAdministrationPublicKey(),
|
|
247
|
-
privateKey: administrationPrivateKey,
|
|
248
|
-
};
|
|
254
|
+
const adminSigningKeyPair = await this.adminKeyPair(administrationPrivateKey, administrationPrivateKeyPassword);
|
|
249
255
|
// Compute username hash (salted v2 for new documents) and encrypted user
|
|
250
256
|
// details before creating the document.
|
|
251
257
|
const usernameHash = await this.hashUsernameForWrite(userId.username);
|
|
@@ -329,8 +335,7 @@ class BaseMindooTenantDirectory {
|
|
|
329
335
|
];
|
|
330
336
|
if (filtered.length === 0)
|
|
331
337
|
return;
|
|
332
|
-
const
|
|
333
|
-
const preparedBy = baseTenant.getAdministrationPublicKey();
|
|
338
|
+
const preparedBy = (await this.adminKeyPair(administrationPrivateKey, administrationPrivateKeyPassword)).publicKey;
|
|
334
339
|
for (const keyId of filtered) {
|
|
335
340
|
const keyVersions = await this.getKeyVersionManifest(keyId);
|
|
336
341
|
if (keyVersions.length === 0) {
|
|
@@ -530,6 +535,21 @@ class BaseMindooTenantDirectory {
|
|
|
530
535
|
return additionalResult;
|
|
531
536
|
}
|
|
532
537
|
}
|
|
538
|
+
// A delegated admin key (docs/accesscontrol.md §6.13) holds no grant; it is
|
|
539
|
+
// trusted while its admin window covers now. Checked before the poll below:
|
|
540
|
+
// the admin-key resolver reads the store directly, while the poll may wait
|
|
541
|
+
// for a directory cache pass that is itself verifying this key's entries.
|
|
542
|
+
if (this.trustedKeysCache.get(publicKey) !== true) {
|
|
543
|
+
if (await this.isAdminKeyNow(publicKey)) {
|
|
544
|
+
this.logger.debug(`Public key is an active delegated admin key, trusted`);
|
|
545
|
+
return true;
|
|
546
|
+
}
|
|
547
|
+
// An ended delegated admin key holds no grant either: nothing to poll for.
|
|
548
|
+
if (await this.tenant.isKnownDelegatedAdminKey(publicKey)) {
|
|
549
|
+
this.logger.debug(`Public key is a delegated admin key outside its window, not trusted now`);
|
|
550
|
+
return false;
|
|
551
|
+
}
|
|
552
|
+
}
|
|
533
553
|
// Store poll: discovers entries this process has not seen yet, so it is
|
|
534
554
|
// throttled to once per interval — unless the caller forces it (audit #4):
|
|
535
555
|
// the server push path must observe a freshly pushed revocation
|
|
@@ -562,10 +582,252 @@ class BaseMindooTenantDirectory {
|
|
|
562
582
|
this.logger.debug(`Public key validation result (from cache): ${cachedResult}`);
|
|
563
583
|
return cachedResult;
|
|
564
584
|
}
|
|
585
|
+
// Not on any grant: it may be a device the person certified (§9.6 of userkeys.md).
|
|
586
|
+
if (await this.resolveSignerToGrant(publicKey, Number.MAX_SAFE_INTEGER)) {
|
|
587
|
+
return true;
|
|
588
|
+
}
|
|
565
589
|
// Key not found in cache means it was never granted access
|
|
566
590
|
this.logger.debug(`Public key not found in cache, returning false`);
|
|
567
591
|
return false;
|
|
568
592
|
}
|
|
593
|
+
// ==================== Delegated administrators (§6.13) ====================
|
|
594
|
+
/** The signing key pair for an admin private key: the root's, or a delegated admin's. */
|
|
595
|
+
async adminKeyPair(administrationPrivateKey, administrationPrivateKeyPassword) {
|
|
596
|
+
const pair = await this.tenant.resolveAdminSigningKeyPair(administrationPrivateKey, administrationPrivateKeyPassword);
|
|
597
|
+
if (!(0, DirectoryStateNode_1.signingKeysEqual)(pair.publicKey, this.getRootAdminPublicKey()) &&
|
|
598
|
+
!(await this.isAdminKeyNow(pair.publicKey))) {
|
|
599
|
+
throw new Error("Signing key pair mismatch: the private key belongs to neither the root admin key nor an active delegated admin of this tenant");
|
|
600
|
+
}
|
|
601
|
+
return pair;
|
|
602
|
+
}
|
|
603
|
+
/** Like {@link adminKeyPair}, but refuses anything but the root admin key. */
|
|
604
|
+
async rootKeyPair(administrationPrivateKey, administrationPrivateKeyPassword, action) {
|
|
605
|
+
const pair = await this.adminKeyPair(administrationPrivateKey, administrationPrivateKeyPassword);
|
|
606
|
+
if (!(0, DirectoryStateNode_1.signingKeysEqual)(pair.publicKey, this.tenant.getAdministrationPublicKey())) {
|
|
607
|
+
throw new Error(`Only the root admin key may ${action}`);
|
|
608
|
+
}
|
|
609
|
+
return pair;
|
|
610
|
+
}
|
|
611
|
+
/**
|
|
612
|
+
* What the trust cache already knows about `publicKey` (true: an active
|
|
613
|
+
* grant key), without polling. Lets entry verification try the store-level
|
|
614
|
+
* admin check first for keys that are not grant keys (§6.13).
|
|
615
|
+
*/
|
|
616
|
+
peekTrustedKey(publicKey) {
|
|
617
|
+
return this.trustedKeysCache.get(publicKey);
|
|
618
|
+
}
|
|
619
|
+
/** The tenant's root admin key (the fixed trust anchor). */
|
|
620
|
+
getRootAdminPublicKey() {
|
|
621
|
+
return this.tenant.getAdministrationPublicKey();
|
|
622
|
+
}
|
|
623
|
+
/**
|
|
624
|
+
* Live admin check (auth, read gate, push check): root, or a delegated admin
|
|
625
|
+
* key whose window covers now. Reads the root-signed admin-key documents from
|
|
626
|
+
* the directory store, so it is safe on the directory's own load path.
|
|
627
|
+
*/
|
|
628
|
+
async isAdminKeyNow(signingKey) {
|
|
629
|
+
return this.tenant.isAdminSigningKeyAt(signingKey, (0, timeSource_1.semanticNow)());
|
|
630
|
+
}
|
|
631
|
+
/**
|
|
632
|
+
* Whether `signingKey` is a tenant admin at trusted time `T` (§6.13): the
|
|
633
|
+
* root always; a delegated admin key while the directory state at `T` holds
|
|
634
|
+
* its admin-key document with `validFrom ≤ T < validUntil`. Pass
|
|
635
|
+
* `Number.MAX_SAFE_INTEGER` for the directory head judged at "now".
|
|
636
|
+
*/
|
|
637
|
+
async isAdminAt(signingKey, T) {
|
|
638
|
+
const root = this.getRootAdminPublicKey();
|
|
639
|
+
if ((0, DirectoryStateNode_1.signingKeysEqual)(signingKey, root))
|
|
640
|
+
return true;
|
|
641
|
+
const node = await this.getDirectoryStateAt(T);
|
|
642
|
+
return (0, DirectoryStateNode_1.isAdminInNode)(node, root, signingKey, (0, adminKeys_1.adminDecisionTime)(T, (0, timeSource_1.semanticNow)()));
|
|
643
|
+
}
|
|
644
|
+
/**
|
|
645
|
+
* Like {@link isAdminAt}, but read from the root-signed admin-key documents in
|
|
646
|
+
* the directory store rather than the time-travel chain (§6.13). This is the
|
|
647
|
+
* form the sync server uses for push and auth decisions: it never waits for a
|
|
648
|
+
* chain rebuild and is safe to call while the directory itself loads.
|
|
649
|
+
*/
|
|
650
|
+
async isAdminSigningKeyAt(signingKey, T) {
|
|
651
|
+
return this.tenant.isAdminSigningKeyAt(signingKey, (0, adminKeys_1.adminDecisionTime)(T, (0, timeSource_1.semanticNow)()));
|
|
652
|
+
}
|
|
653
|
+
/**
|
|
654
|
+
* The delegated admin key `signingKey` as valid at `T` (its encryption key and
|
|
655
|
+
* window), or null when it is not a delegated admin then. Store-level like
|
|
656
|
+
* {@link isAdminSigningKeyAt}; the sync server uses it to let an admin key
|
|
657
|
+
* authenticate without a grant.
|
|
658
|
+
*/
|
|
659
|
+
async getDelegatedAdminKeyAt(signingKey, T) {
|
|
660
|
+
const tenant = this.tenant;
|
|
661
|
+
if (!(await tenant.isAdminSigningKeyAt(signingKey, (0, adminKeys_1.adminDecisionTime)(T, (0, timeSource_1.semanticNow)()))))
|
|
662
|
+
return null;
|
|
663
|
+
const directoryDB = await this.getDirectoryDB();
|
|
664
|
+
return tenant
|
|
665
|
+
.getDirectoryAdminResolver(directoryDB.getStore())
|
|
666
|
+
.delegatedAdminKeyAt(signingKey, (0, adminKeys_1.adminDecisionTime)(T, (0, timeSource_1.semanticNow)()));
|
|
667
|
+
}
|
|
668
|
+
/**
|
|
669
|
+
* The delegated admin keys at the directory head, with their windows, names
|
|
670
|
+
* (when this replica holds `default`) and status at "now". `pending` is true
|
|
671
|
+
* while the latest revision of a key's document carries no witness receipt
|
|
672
|
+
* yet: other replicas do not see it until a trusted witness stamped it.
|
|
673
|
+
*/
|
|
674
|
+
async listAdminKeys() {
|
|
675
|
+
return this.adminKeysFromNode(await this.getDirectoryStateHead(), (0, timeSource_1.semanticNow)(), true);
|
|
676
|
+
}
|
|
677
|
+
/** The delegated admin keys as the directory knew them at trusted time `T`. */
|
|
678
|
+
async getAdminKeysAt(T) {
|
|
679
|
+
return this.adminKeysFromNode(await this.getDirectoryStateAt(T), (0, adminKeys_1.adminDecisionTime)(T, (0, timeSource_1.semanticNow)()), false);
|
|
680
|
+
}
|
|
681
|
+
async adminKeysFromNode(node, at, withPending) {
|
|
682
|
+
const directoryDB = await this.getDirectoryDB();
|
|
683
|
+
const subtle = this.tenant.getCryptoAdapter().getSubtle();
|
|
684
|
+
const out = [];
|
|
685
|
+
for (const snapshot of node.adminKeys?.values() ?? []) {
|
|
686
|
+
const docId = await (0, adminKeys_1.aclAdminKeyDocIdForKey)(snapshot.signingPublicKey, subtle);
|
|
687
|
+
let name;
|
|
688
|
+
try {
|
|
689
|
+
const doc = await directoryDB.getDocument(docId);
|
|
690
|
+
const decrypted = await (0, encryptedFields_1.decryptEncryptedField)(this.tenant, doc.getData(), "name_encrypted");
|
|
691
|
+
if (typeof decrypted === "string" && decrypted.trim())
|
|
692
|
+
name = decrypted.trim();
|
|
693
|
+
}
|
|
694
|
+
catch {
|
|
695
|
+
// No `default` key here, or the document is not materialized.
|
|
696
|
+
}
|
|
697
|
+
let pending = false;
|
|
698
|
+
if (withPending) {
|
|
699
|
+
try {
|
|
700
|
+
const entries = await directoryDB.getStore().findNewEntriesForDoc([], docId);
|
|
701
|
+
const latest = entries
|
|
702
|
+
.filter((e) => (0, DirectoryStateNode_1.signingKeysEqual)(e.createdByPublicKey, this.getRootAdminPublicKey()))
|
|
703
|
+
.sort((a, b) => a.createdAt - b.createdAt)
|
|
704
|
+
.pop();
|
|
705
|
+
pending = !!latest && latest.receivedAt === undefined;
|
|
706
|
+
}
|
|
707
|
+
catch {
|
|
708
|
+
pending = false;
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
out.push({
|
|
712
|
+
...snapshot,
|
|
713
|
+
...(name ? { name } : {}),
|
|
714
|
+
status: (0, adminKeys_1.adminKeyStatusAt)(snapshot, at),
|
|
715
|
+
active: (0, adminKeys_1.isAdminKeySnapshotValidAt)(snapshot, at),
|
|
716
|
+
pending,
|
|
717
|
+
});
|
|
718
|
+
}
|
|
719
|
+
return out.sort((a, b) => a.validFrom - b.validFrom);
|
|
720
|
+
}
|
|
721
|
+
/**
|
|
722
|
+
* Make `signingPublicKey` a delegated admin (§6.13). Root only. Writes the
|
|
723
|
+
* `acl_adminkey_<fingerprint>` document (under `$publicinfos`) and records the
|
|
724
|
+
* admin in `tenantsetup.administrators` for display. Adding a key whose
|
|
725
|
+
* document already exists starts a new window (reactivation): entries signed
|
|
726
|
+
* between the old end and the new start stay invalid.
|
|
727
|
+
*
|
|
728
|
+
* The key becomes effective at the witness time of this write (or at
|
|
729
|
+
* `validFrom`, if later). Other replicas honor it once a trusted witness has
|
|
730
|
+
* stamped the document.
|
|
731
|
+
*/
|
|
732
|
+
async addAdminKey(admin, administrationPrivateKey, administrationPrivateKeyPassword) {
|
|
733
|
+
const rootPair = await this.rootKeyPair(administrationPrivateKey, administrationPrivateKeyPassword, "add administrators");
|
|
734
|
+
const name = admin.name.trim();
|
|
735
|
+
if (!name)
|
|
736
|
+
throw new Error("addAdminKey: name is required");
|
|
737
|
+
if (!admin.signingPublicKey.trim() || !admin.encryptionPublicKey.trim()) {
|
|
738
|
+
throw new Error("addAdminKey: signingPublicKey and encryptionPublicKey are required");
|
|
739
|
+
}
|
|
740
|
+
if ((0, DirectoryStateNode_1.signingKeysEqual)(admin.signingPublicKey, rootPair.publicKey)) {
|
|
741
|
+
throw new Error("addAdminKey: the root admin key is always an administrator");
|
|
742
|
+
}
|
|
743
|
+
const directoryDB = await this.getDirectoryDB();
|
|
744
|
+
const subtle = this.tenant.getCryptoAdapter().getSubtle();
|
|
745
|
+
const docId = await (0, adminKeys_1.aclAdminKeyDocIdForKey)(admin.signingPublicKey, subtle);
|
|
746
|
+
const nameEncrypted = await (0, encryptedFields_1.encryptEncryptedField)(this.tenant, name);
|
|
747
|
+
const label = admin.label?.trim();
|
|
748
|
+
const doc = await this.getOrCreateAclDoc(directoryDB, docId, rootPair, administrationPrivateKeyPassword);
|
|
749
|
+
await directoryDB.changeDoc(doc, async (d) => {
|
|
750
|
+
const data = d.getData();
|
|
751
|
+
data.form = types_3.ACCESS_CONTROL_FORM;
|
|
752
|
+
data.type = adminKeys_1.ADMIN_KEY_TYPE;
|
|
753
|
+
data.signingPublicKey = admin.signingPublicKey;
|
|
754
|
+
data.encryptionPublicKey = admin.encryptionPublicKey;
|
|
755
|
+
data.name_encrypted = nameEncrypted;
|
|
756
|
+
data.name_encrypted_key = types_1.DEFAULT_TENANT_KEY_ID;
|
|
757
|
+
if (label)
|
|
758
|
+
data.label = label;
|
|
759
|
+
else
|
|
760
|
+
delete data.label;
|
|
761
|
+
if (typeof admin.validFrom === "number" && Number.isFinite(admin.validFrom)) {
|
|
762
|
+
data.validFrom = admin.validFrom;
|
|
763
|
+
}
|
|
764
|
+
else {
|
|
765
|
+
delete data.validFrom;
|
|
766
|
+
}
|
|
767
|
+
delete data.validUntil;
|
|
768
|
+
}, { signingKeyPair: rootPair, signingKeyPassword: administrationPrivateKeyPassword });
|
|
769
|
+
try {
|
|
770
|
+
await (0, tenantSetup_1.ensureTenantSetupAdministrator)(directoryDB, {
|
|
771
|
+
username: name,
|
|
772
|
+
keyPairs: [
|
|
773
|
+
{
|
|
774
|
+
signingPublicKey: admin.signingPublicKey,
|
|
775
|
+
encryptionPublicKey: admin.encryptionPublicKey,
|
|
776
|
+
...(label ? { label } : {}),
|
|
777
|
+
addedAt: (0, timeSource_1.semanticNow)(),
|
|
778
|
+
},
|
|
779
|
+
],
|
|
780
|
+
}, rootPair, administrationPrivateKeyPassword, this.tenant);
|
|
781
|
+
}
|
|
782
|
+
catch (error) {
|
|
783
|
+
this.logger.warn(`addAdminKey: recording the administrator in tenantsetup failed: ${error}`);
|
|
784
|
+
}
|
|
785
|
+
await this.updateUnifiedCache();
|
|
786
|
+
}
|
|
787
|
+
/**
|
|
788
|
+
* Change the window of a delegated admin key (§6.13). Root only.
|
|
789
|
+
*
|
|
790
|
+
* - `validUntil: <ms>` deactivates the key as of that witness time; a future
|
|
791
|
+
* date schedules the end. A date before the witness time of this write is
|
|
792
|
+
* raised to it — deactivation never reaches back.
|
|
793
|
+
* - `validUntil: null` removes the end. On a key that has already ended this
|
|
794
|
+
* reactivates it from the witness time of this write on.
|
|
795
|
+
* - `validFrom: <ms>` / `null` sets or clears a later start.
|
|
796
|
+
*/
|
|
797
|
+
async setAdminKeyValidity(signingPublicKey, validity, administrationPrivateKey, administrationPrivateKeyPassword) {
|
|
798
|
+
const rootPair = await this.rootKeyPair(administrationPrivateKey, administrationPrivateKeyPassword, "change administrators");
|
|
799
|
+
const directoryDB = await this.getDirectoryDB();
|
|
800
|
+
const docId = await (0, adminKeys_1.aclAdminKeyDocIdForKey)(signingPublicKey, this.tenant.getCryptoAdapter().getSubtle());
|
|
801
|
+
let doc;
|
|
802
|
+
try {
|
|
803
|
+
doc = await directoryDB.getDocument(docId);
|
|
804
|
+
}
|
|
805
|
+
catch {
|
|
806
|
+
throw new Error("setAdminKeyValidity: this key is not a delegated administrator");
|
|
807
|
+
}
|
|
808
|
+
const ended = (() => {
|
|
809
|
+
const data = doc.getData();
|
|
810
|
+
return typeof data.validUntil === "number" && data.validUntil <= (0, timeSource_1.semanticNow)();
|
|
811
|
+
})();
|
|
812
|
+
await directoryDB.changeDoc(doc, async (d) => {
|
|
813
|
+
const data = d.getData();
|
|
814
|
+
if (validity.validUntil === null)
|
|
815
|
+
delete data.validUntil;
|
|
816
|
+
else if (typeof validity.validUntil === "number" && Number.isFinite(validity.validUntil)) {
|
|
817
|
+
data.validUntil = validity.validUntil;
|
|
818
|
+
}
|
|
819
|
+
if (validity.validFrom === null)
|
|
820
|
+
delete data.validFrom;
|
|
821
|
+
else if (typeof validity.validFrom === "number" && Number.isFinite(validity.validFrom)) {
|
|
822
|
+
data.validFrom = validity.validFrom;
|
|
823
|
+
}
|
|
824
|
+
else if (validity.validUntil === null && ended) {
|
|
825
|
+
// Reactivation: a new window starting at this revision's witness time.
|
|
826
|
+
delete data.validFrom;
|
|
827
|
+
}
|
|
828
|
+
}, { signingKeyPair: rootPair, signingKeyPassword: administrationPrivateKeyPassword });
|
|
829
|
+
await this.updateUnifiedCache();
|
|
830
|
+
}
|
|
569
831
|
/**
|
|
570
832
|
* Whether a cache miss for `publicKey` may poll the store. A key that missed
|
|
571
833
|
* within the last DIRECTORY_SYNC_INTERVAL_MS does not poll again; it still
|
|
@@ -1093,6 +1355,7 @@ class BaseMindooTenantDirectory {
|
|
|
1093
1355
|
const node = await this.getDirectoryStateAt(input.trustedTime);
|
|
1094
1356
|
const identity = await this.identitySetForSigningKey(input.signingKey, node, {
|
|
1095
1357
|
isAuthor: input.op === "doc_create" || !!input.isAuthor,
|
|
1358
|
+
trustedTime: input.trustedTime,
|
|
1096
1359
|
});
|
|
1097
1360
|
return (0, evaluate_1.evaluateAccess)({
|
|
1098
1361
|
op: input.op,
|
|
@@ -1112,6 +1375,7 @@ class BaseMindooTenantDirectory {
|
|
|
1112
1375
|
const node = await this.getDirectoryStateAt(input.trustedTime);
|
|
1113
1376
|
const identity = await this.identitySetForSigningKey(input.signingKey, node, {
|
|
1114
1377
|
isAuthor: input.op === "doc_create" || input.isAuthor,
|
|
1378
|
+
trustedTime: input.trustedTime,
|
|
1115
1379
|
});
|
|
1116
1380
|
return (0, evaluate_1.evaluateAccess)({
|
|
1117
1381
|
op: input.op,
|
|
@@ -1132,15 +1396,283 @@ class BaseMindooTenantDirectory {
|
|
|
1132
1396
|
const signerNode = input.signerTrustedTime === input.creatorTrustedTime
|
|
1133
1397
|
? creatorNode
|
|
1134
1398
|
: await this.getDirectoryStateAt(input.signerTrustedTime);
|
|
1135
|
-
|
|
1399
|
+
if ((0, DirectoryStateNode_1.isSamePerson)(creatorNode, signerNode, input.creatorSigningKey, input.signerSigningKey)) {
|
|
1400
|
+
return true;
|
|
1401
|
+
}
|
|
1402
|
+
// Certified devices (userkeys.md §9.6): compare the persons they act for.
|
|
1403
|
+
// The creator is looked up without revocation, like a retired grant device.
|
|
1404
|
+
const creatorHash = (0, DirectoryStateNode_1.grantAtOrBefore)(creatorNode, input.creatorSigningKey)?.usernameHash ??
|
|
1405
|
+
(await this.certifiedGrantInNode(creatorNode, input.creatorSigningKey, input.creatorTrustedTime, {
|
|
1406
|
+
enforceRevocation: false,
|
|
1407
|
+
}))?.grant.usernameHash;
|
|
1408
|
+
const signerHash = (await this.resolveSignerInNode(signerNode, input.signerSigningKey, input.signerTrustedTime))?.grant.usernameHash;
|
|
1409
|
+
return !!creatorHash && creatorHash === signerHash;
|
|
1136
1410
|
}
|
|
1137
1411
|
/**
|
|
1138
1412
|
* The `username_hash` of the active grant that contained `signingKey` at
|
|
1139
1413
|
* trusted time `T`, or `null` when the key was not a granted device then.
|
|
1140
1414
|
*/
|
|
1141
1415
|
async resolveUsernameHashForSigningKey(signingKey, trustedTime) {
|
|
1416
|
+
return (await this.resolveSignerToGrant(signingKey, trustedTime))?.grant.usernameHash ?? null;
|
|
1417
|
+
}
|
|
1418
|
+
/**
|
|
1419
|
+
* The grant a signing key acts under at trusted time `T`
|
|
1420
|
+
* (docs/userkeys.md §9.6): a device listed on the grant, or a device with a
|
|
1421
|
+
* valid certificate. A certificate counts when
|
|
1422
|
+
*
|
|
1423
|
+
* - it verifies with the self-grant key that the person's grant names at `T`,
|
|
1424
|
+
* - its `doc_create` was accepted (trusted time) at or before `T`, and
|
|
1425
|
+
* before the delegation's `revokedAt`,
|
|
1426
|
+
* - the person did not delete it at or before `T`, and
|
|
1427
|
+
* - the device is not in the grant's `revokedUserKeyPairs` at `T`.
|
|
1428
|
+
*
|
|
1429
|
+
* The one place that answers "which person is this key"; server auth, ACL
|
|
1430
|
+
* evaluation, ownership and attribution go through it.
|
|
1431
|
+
*/
|
|
1432
|
+
async resolveSignerToGrant(signingKey, trustedTime) {
|
|
1142
1433
|
const node = await this.getDirectoryStateAt(trustedTime);
|
|
1143
|
-
return
|
|
1434
|
+
return this.resolveSignerInNode(node, signingKey, trustedTime);
|
|
1435
|
+
}
|
|
1436
|
+
async resolveSignerInNode(node, signingKey, trustedTime) {
|
|
1437
|
+
const listed = (0, DirectoryStateNode_1.grantForSigningKey)(node, signingKey);
|
|
1438
|
+
if (listed)
|
|
1439
|
+
return { grant: listed, via: "grant" };
|
|
1440
|
+
const certified = await this.certifiedGrantInNode(node, signingKey, trustedTime, {
|
|
1441
|
+
enforceRevocation: true,
|
|
1442
|
+
});
|
|
1443
|
+
return certified ? { grant: certified.grant, via: "certificate", certificate: certified.cert } : null;
|
|
1444
|
+
}
|
|
1445
|
+
/** Device certificates valid for `signingKey` in `node` (see {@link resolveSignerToGrant}). */
|
|
1446
|
+
async certifiedGrantInNode(node, signingKey, trustedTime, options) {
|
|
1447
|
+
if (!signingKey)
|
|
1448
|
+
return null;
|
|
1449
|
+
const records = (await this.loadDeviceCertificateIndex()).get((0, fingerprint_1.normalizePublicKeyPem)(signingKey));
|
|
1450
|
+
if (!records?.length)
|
|
1451
|
+
return null;
|
|
1452
|
+
const now = Date.now();
|
|
1453
|
+
for (const record of records) {
|
|
1454
|
+
const grant = node.usersByHash.get(record.cert.username_hash);
|
|
1455
|
+
if (!grant)
|
|
1456
|
+
continue;
|
|
1457
|
+
const delegation = grant.deviceSelfGrant;
|
|
1458
|
+
if (!delegation ||
|
|
1459
|
+
(0, fingerprint_1.normalizePublicKeyPem)(delegation.signingPublicKey) !==
|
|
1460
|
+
(0, fingerprint_1.normalizePublicKeyPem)(record.cert.selfGrantPublicKey)) {
|
|
1461
|
+
continue;
|
|
1462
|
+
}
|
|
1463
|
+
const issuedAt = (0, storeEntryTime_1.entryTrustedTime)(record.createMeta, now);
|
|
1464
|
+
if (delegation.revokedAt !== undefined && issuedAt >= delegation.revokedAt)
|
|
1465
|
+
continue;
|
|
1466
|
+
if (options.enforceRevocation) {
|
|
1467
|
+
if (!grant.active)
|
|
1468
|
+
continue;
|
|
1469
|
+
if (issuedAt > trustedTime)
|
|
1470
|
+
continue;
|
|
1471
|
+
const deletedAt = record.deleteMetas.length
|
|
1472
|
+
? Math.min(...record.deleteMetas.map((m) => (0, storeEntryTime_1.entryTrustedTime)(m, now)))
|
|
1473
|
+
: undefined;
|
|
1474
|
+
if (deletedAt !== undefined && deletedAt <= trustedTime)
|
|
1475
|
+
continue;
|
|
1476
|
+
if ((grant.revokedSigningKeys ?? []).some((key) => (0, DirectoryStateNode_1.signingKeysEqual)(key, signingKey)))
|
|
1477
|
+
continue;
|
|
1478
|
+
}
|
|
1479
|
+
return { grant, cert: record.cert };
|
|
1480
|
+
}
|
|
1481
|
+
return null;
|
|
1482
|
+
}
|
|
1483
|
+
/**
|
|
1484
|
+
* Every certificate of the person with `usernameHash` that their current
|
|
1485
|
+
* self-grant key signed, including revoked devices (for revoke and wipe).
|
|
1486
|
+
*/
|
|
1487
|
+
async certificatesOfPerson(usernameHash) {
|
|
1488
|
+
const head = await this.getDirectoryStateHead();
|
|
1489
|
+
const result = [];
|
|
1490
|
+
for (const records of (await this.loadDeviceCertificateIndex()).values()) {
|
|
1491
|
+
const record = records.find((r) => r.cert.username_hash === usernameHash);
|
|
1492
|
+
if (!record)
|
|
1493
|
+
continue;
|
|
1494
|
+
const hit = await this.certifiedGrantInNode(head, record.cert.deviceSigningPublicKey, Number.MAX_SAFE_INTEGER, {
|
|
1495
|
+
enforceRevocation: false,
|
|
1496
|
+
});
|
|
1497
|
+
if (hit?.grant.usernameHash === usernameHash)
|
|
1498
|
+
result.push(hit.cert);
|
|
1499
|
+
}
|
|
1500
|
+
return result;
|
|
1501
|
+
}
|
|
1502
|
+
/**
|
|
1503
|
+
* Certified devices of the person with `usernameHash` that count at the head
|
|
1504
|
+
* state, with their certificates.
|
|
1505
|
+
*/
|
|
1506
|
+
async listCertifiedDevices(usernameHash) {
|
|
1507
|
+
const head = await this.getDirectoryStateHead();
|
|
1508
|
+
const index = await this.loadDeviceCertificateIndex();
|
|
1509
|
+
const result = [];
|
|
1510
|
+
const seen = new Set();
|
|
1511
|
+
for (const [key, records] of index) {
|
|
1512
|
+
if (!records.some((r) => r.cert.username_hash === usernameHash))
|
|
1513
|
+
continue;
|
|
1514
|
+
const hit = await this.certifiedGrantInNode(head, records[0].cert.deviceSigningPublicKey, Number.MAX_SAFE_INTEGER, {
|
|
1515
|
+
enforceRevocation: true,
|
|
1516
|
+
});
|
|
1517
|
+
if (hit && hit.grant.usernameHash === usernameHash && !seen.has(key)) {
|
|
1518
|
+
seen.add(key);
|
|
1519
|
+
result.push(hit.cert);
|
|
1520
|
+
}
|
|
1521
|
+
}
|
|
1522
|
+
return result;
|
|
1523
|
+
}
|
|
1524
|
+
/**
|
|
1525
|
+
* Every device certificate in `userdirectory`, with the trusted time its
|
|
1526
|
+
* `doc_create` was accepted (`issuedAt`) and, when the person withdrew it,
|
|
1527
|
+
* the trusted time of the first delete (`deletedAt`) (docs/userkeys.md §9.6).
|
|
1528
|
+
*
|
|
1529
|
+
* Unlike {@link listCertifiedDevices} nothing is filtered: revoked devices,
|
|
1530
|
+
* withdrawn certificates and certificates of revoked delegations are listed
|
|
1531
|
+
* too, so audit views can work out which certified devices a person had at
|
|
1532
|
+
* any point in time. Whether a certificate is valid at `T` is still decided
|
|
1533
|
+
* by {@link resolveSignerToGrant}.
|
|
1534
|
+
*/
|
|
1535
|
+
async listDeviceCertificates() {
|
|
1536
|
+
const index = await this.loadDeviceCertificateIndex();
|
|
1537
|
+
const now = Date.now();
|
|
1538
|
+
const result = [];
|
|
1539
|
+
for (const records of index.values()) {
|
|
1540
|
+
for (const record of records) {
|
|
1541
|
+
const deletedAt = record.deleteMetas.length
|
|
1542
|
+
? Math.min(...record.deleteMetas.map((meta) => (0, storeEntryTime_1.entryTrustedTime)(meta, now)))
|
|
1543
|
+
: undefined;
|
|
1544
|
+
result.push({
|
|
1545
|
+
certificate: record.cert,
|
|
1546
|
+
issuedAt: (0, storeEntryTime_1.entryTrustedTime)(record.createMeta, now),
|
|
1547
|
+
...(deletedAt !== undefined ? { deletedAt } : {}),
|
|
1548
|
+
});
|
|
1549
|
+
}
|
|
1550
|
+
}
|
|
1551
|
+
return result.sort((a, b) => a.issuedAt - b.issuedAt);
|
|
1552
|
+
}
|
|
1553
|
+
/**
|
|
1554
|
+
* Read every `dcert_*` certificate from the `userdirectory` store without
|
|
1555
|
+
* materializing that database (whose own trust checks call back into this
|
|
1556
|
+
* index). Only `doc_create` entries carry a certificate; deletes withdraw
|
|
1557
|
+
* it. Rebuilt when the store head moves, at most every two seconds for
|
|
1558
|
+
* stores that cannot report a head.
|
|
1559
|
+
*/
|
|
1560
|
+
async loadDeviceCertificateIndex() {
|
|
1561
|
+
if (this.buildingDeviceCertIndex) {
|
|
1562
|
+
// Re-entry while building: answer from what is known.
|
|
1563
|
+
return this.deviceCertIndex?.byKey ?? new Map();
|
|
1564
|
+
}
|
|
1565
|
+
const baseTenant = this.tenant;
|
|
1566
|
+
if (typeof baseTenant.getDocStoreForDb !== "function")
|
|
1567
|
+
return new Map();
|
|
1568
|
+
let store;
|
|
1569
|
+
try {
|
|
1570
|
+
store = baseTenant.getDocStoreForDb(types_1.USER_DIRECTORY_DB_ID);
|
|
1571
|
+
}
|
|
1572
|
+
catch {
|
|
1573
|
+
return new Map();
|
|
1574
|
+
}
|
|
1575
|
+
const head = await store.getStoreHead?.().catch(() => undefined);
|
|
1576
|
+
const stamp = head ? `${head.epoch}:${head.maxReceiptOrder}` : null;
|
|
1577
|
+
const cached = this.deviceCertIndex;
|
|
1578
|
+
if (cached && stamp !== null && cached.stamp === stamp)
|
|
1579
|
+
return cached.byKey;
|
|
1580
|
+
if (cached && stamp === null && Date.now() - cached.at < 2000)
|
|
1581
|
+
return cached.byKey;
|
|
1582
|
+
const build = (async () => {
|
|
1583
|
+
const byKey = new Map();
|
|
1584
|
+
const isCert = (m) => m.docId.startsWith(deviceCertificate_1.DEVICE_CERT_DOC_ID_PREFIX);
|
|
1585
|
+
const creates = (await store.findEntries("doc_create", null, null)).filter(isCert);
|
|
1586
|
+
if (creates.length === 0)
|
|
1587
|
+
return byKey;
|
|
1588
|
+
const deletes = (await store.findEntries("doc_delete", null, null)).filter(isCert);
|
|
1589
|
+
const entries = await store.getEntries(creates.map((m) => m.id));
|
|
1590
|
+
const crypto = this.tenant.getCryptoAdapter();
|
|
1591
|
+
for (const entry of entries) {
|
|
1592
|
+
try {
|
|
1593
|
+
const plaintext = await baseTenant.decryptPayload(entry.encryptedData, entry.decryptionKeyId);
|
|
1594
|
+
const cert = (0, deviceCertificate_1.asDeviceCertificate)((0, builtinDbInvariants_1.recordFromCreateChangeBytes)(plaintext));
|
|
1595
|
+
if (!cert || !(await (0, deviceCertificate_1.verifyDeviceCertificate)(crypto, cert)))
|
|
1596
|
+
continue;
|
|
1597
|
+
const createMeta = creates.find((m) => m.id === entry.id) ?? entry;
|
|
1598
|
+
const key = (0, fingerprint_1.normalizePublicKeyPem)(cert.deviceSigningPublicKey);
|
|
1599
|
+
const list = byKey.get(key) ?? [];
|
|
1600
|
+
list.push({
|
|
1601
|
+
cert,
|
|
1602
|
+
docId: entry.docId,
|
|
1603
|
+
createMeta,
|
|
1604
|
+
deleteMetas: deletes.filter((m) => m.docId === entry.docId),
|
|
1605
|
+
});
|
|
1606
|
+
byKey.set(key, list);
|
|
1607
|
+
}
|
|
1608
|
+
catch {
|
|
1609
|
+
// Not readable here (no $publicinfos) or not a certificate.
|
|
1610
|
+
}
|
|
1611
|
+
}
|
|
1612
|
+
return byKey;
|
|
1613
|
+
})();
|
|
1614
|
+
this.buildingDeviceCertIndex = build;
|
|
1615
|
+
try {
|
|
1616
|
+
const byKey = await build;
|
|
1617
|
+
this.deviceCertIndex = { stamp, at: Date.now(), byKey };
|
|
1618
|
+
return byKey;
|
|
1619
|
+
}
|
|
1620
|
+
finally {
|
|
1621
|
+
this.buildingDeviceCertIndex = null;
|
|
1622
|
+
}
|
|
1623
|
+
}
|
|
1624
|
+
/**
|
|
1625
|
+
* The person's device self-grant delegation at the head state
|
|
1626
|
+
* (docs/userkeys.md §9), or `null` when the admin never granted it.
|
|
1627
|
+
*/
|
|
1628
|
+
async getDeviceSelfGrantForUsernameHash(usernameHash) {
|
|
1629
|
+
const head = await this.getDirectoryStateHead();
|
|
1630
|
+
return head.usersByHash.get(usernameHash)?.deviceSelfGrant ?? null;
|
|
1631
|
+
}
|
|
1632
|
+
/** {@link getDeviceSelfGrantForUsernameHash} by username (newest grant wins). */
|
|
1633
|
+
async getDeviceSelfGrant(username) {
|
|
1634
|
+
const grants = await this.findGrantAccessDocuments(username);
|
|
1635
|
+
return (0, grantKeys_1.newestDeviceSelfGrant)(grants.map((g) => (0, grantKeys_1.extractDeviceSelfGrant)(g.getData())));
|
|
1636
|
+
}
|
|
1637
|
+
/**
|
|
1638
|
+
* Grant or revoke the person's right to certify their own devices
|
|
1639
|
+
* (docs/userkeys.md §9). `signingPublicKey` is the public half of their
|
|
1640
|
+
* device self-grant key (from the join request, or
|
|
1641
|
+
* `tenant.getDeviceSelfGrantPublicKey()` on one of their devices). `null`
|
|
1642
|
+
* revokes: the key stays on the grant with `revokedAt`, so certificates and
|
|
1643
|
+
* generations signed before stay valid; certificates issued later do not.
|
|
1644
|
+
*/
|
|
1645
|
+
async setDeviceSelfGrant(username, signingPublicKey, administrationPrivateKey, administrationPrivateKeyPassword) {
|
|
1646
|
+
await this.updateUserGrant(username, { deviceSelfGrant: signingPublicKey }, administrationPrivateKey, administrationPrivateKeyPassword);
|
|
1647
|
+
}
|
|
1648
|
+
/**
|
|
1649
|
+
* Trusted time from which new User-Key generations must be signed
|
|
1650
|
+
* (`requireUserKeyGenerationSignatureSince`, docs/userkeys.md §9.3), or
|
|
1651
|
+
* `undefined`. Read from the `acl_defaultpolicy` head regardless of the
|
|
1652
|
+
* master switch, like {@link getRequireMetadataSignatureSince}.
|
|
1653
|
+
*/
|
|
1654
|
+
async getRequireUserKeyGenerationSignatureSince() {
|
|
1655
|
+
const head = await this.getDirectoryStateHead();
|
|
1656
|
+
const since = head.defaultPolicy?.requireUserKeyGenerationSignatureSince;
|
|
1657
|
+
return typeof since === "number" && Number.isFinite(since) ? since : undefined;
|
|
1658
|
+
}
|
|
1659
|
+
/**
|
|
1660
|
+
* Require signed User-Key generations from now on (docs/userkeys.md §9.3).
|
|
1661
|
+
* Idempotent: an existing cutoff is kept. Run it once every device of the
|
|
1662
|
+
* tenant runs a client that signs generations; unsigned generations
|
|
1663
|
+
* introduced before stay valid.
|
|
1664
|
+
*/
|
|
1665
|
+
async requireUserKeyGenerationSignatures(administrationPrivateKey, administrationPrivateKeyPassword) {
|
|
1666
|
+
const head = await this.getDirectoryStateHead();
|
|
1667
|
+
const existing = head.defaultPolicy?.requireUserKeyGenerationSignatureSince;
|
|
1668
|
+
if (typeof existing === "number" && Number.isFinite(existing)) {
|
|
1669
|
+
return { since: existing, changed: false };
|
|
1670
|
+
}
|
|
1671
|
+
const since = (0, timeSource_1.semanticNow)();
|
|
1672
|
+
await this.setDefaultAccessPolicy(head.defaultPolicy
|
|
1673
|
+
? { requireUserKeyGenerationSignatureSince: since }
|
|
1674
|
+
: { disableAllAccessChecksAndPolicies: true, requireUserKeyGenerationSignatureSince: since }, administrationPrivateKey, administrationPrivateKeyPassword);
|
|
1675
|
+
return { since, changed: true };
|
|
1144
1676
|
}
|
|
1145
1677
|
/**
|
|
1146
1678
|
* Whether the active policy has any Tier 2 (`withfields`) content rule for
|
|
@@ -1242,8 +1774,7 @@ class BaseMindooTenantDirectory {
|
|
|
1242
1774
|
*/
|
|
1243
1775
|
async canUseLlm() {
|
|
1244
1776
|
const currentUser = await this.tenant.getCurrentUserId();
|
|
1245
|
-
|
|
1246
|
-
if (currentUser.userSigningPublicKey === adminKey) {
|
|
1777
|
+
if (await this.isAdminKeyNow(currentUser.userSigningPublicKey)) {
|
|
1247
1778
|
return { allowed: true, reason: "tenant admin", tier: "tier1" };
|
|
1248
1779
|
}
|
|
1249
1780
|
// Head selector, NOT Date.now() — see canDo.
|
|
@@ -1310,8 +1841,7 @@ class BaseMindooTenantDirectory {
|
|
|
1310
1841
|
if (allowedDbIds.includes(dbid))
|
|
1311
1842
|
return true;
|
|
1312
1843
|
if (opts?.signingKey !== undefined) {
|
|
1313
|
-
|
|
1314
|
-
if (opts.signingKey === adminKey)
|
|
1844
|
+
if (await this.isAdminKeyNow(opts.signingKey))
|
|
1315
1845
|
return true;
|
|
1316
1846
|
}
|
|
1317
1847
|
return false;
|
|
@@ -1335,9 +1865,8 @@ class BaseMindooTenantDirectory {
|
|
|
1335
1865
|
// policy that defines the read gate can be read at all).
|
|
1336
1866
|
if (input.dbid === types_1.DIRECTORY_DB_ID || input.dbid === types_1.USER_DIRECTORY_DB_ID)
|
|
1337
1867
|
return true;
|
|
1338
|
-
//
|
|
1339
|
-
|
|
1340
|
-
if (input.signingKey === adminKey)
|
|
1868
|
+
// Tenant admins (root and active delegated admins, §6.13) are exempt from the read gate.
|
|
1869
|
+
if (await this.isAdminKeyNow(input.signingKey))
|
|
1341
1870
|
return true;
|
|
1342
1871
|
// Gate 2 — per-user/group database read gate (`doc_read`, §6.6). Evaluated
|
|
1343
1872
|
// server-side (Tier 1) from the principal's signing key at acceptance time.
|
|
@@ -1363,8 +1892,7 @@ class BaseMindooTenantDirectory {
|
|
|
1363
1892
|
async evaluateReadScopeForSigningKey(input) {
|
|
1364
1893
|
if (input.dbid === types_1.DIRECTORY_DB_ID || input.dbid === types_1.USER_DIRECTORY_DB_ID)
|
|
1365
1894
|
return "all";
|
|
1366
|
-
|
|
1367
|
-
if (input.signingKey === adminKey)
|
|
1895
|
+
if (await this.isAdminKeyNow(input.signingKey))
|
|
1368
1896
|
return "all";
|
|
1369
1897
|
const decision = await this.evaluateAccessForSigningKey({
|
|
1370
1898
|
op: "doc_read_all",
|
|
@@ -1397,7 +1925,7 @@ class BaseMindooTenantDirectory {
|
|
|
1397
1925
|
if (personal) {
|
|
1398
1926
|
if (personal.mode === "none")
|
|
1399
1927
|
return personal;
|
|
1400
|
-
if (
|
|
1928
|
+
if (await this.isAdminKeyNow(input.signingKey)) {
|
|
1401
1929
|
return personal;
|
|
1402
1930
|
}
|
|
1403
1931
|
if (personal.mode === "read") {
|
|
@@ -1453,7 +1981,7 @@ class BaseMindooTenantDirectory {
|
|
|
1453
1981
|
const owner = (0, builtinDbInvariants_1.personalDatabaseOwner)(input.dbid, head.defaultPolicy?.personalDatabasePrefixes ?? []);
|
|
1454
1982
|
if (!owner)
|
|
1455
1983
|
return null;
|
|
1456
|
-
const isAdmin =
|
|
1984
|
+
const isAdmin = await this.isAdminKeyNow(input.signingKey);
|
|
1457
1985
|
if (isAdmin && owner.prefix !== "mbusy_") {
|
|
1458
1986
|
return { mode: "read", reason: "tenant admin" };
|
|
1459
1987
|
}
|
|
@@ -1461,6 +1989,12 @@ class BaseMindooTenantDirectory {
|
|
|
1461
1989
|
if (grant && (0, grantKeys_1.extractActiveKeyPairs)(grant).some((pair) => (0, DirectoryStateNode_1.signingKeysEqual)(pair.signingPublicKey, input.signingKey))) {
|
|
1462
1990
|
return { mode: "read", reason: "a device of the owner" };
|
|
1463
1991
|
}
|
|
1992
|
+
if (grant && typeof grant.username_hash === "string") {
|
|
1993
|
+
const certified = await this.resolveSignerInNode(head, input.signingKey, Number.MAX_SAFE_INTEGER);
|
|
1994
|
+
if (certified?.via === "certificate" && certified.grant.usernameHash === grant.username_hash) {
|
|
1995
|
+
return { mode: "read", reason: "a certified device of the owner" };
|
|
1996
|
+
}
|
|
1997
|
+
}
|
|
1464
1998
|
// Free/busy (§6.11): the tenant admin reads every person's busy times but,
|
|
1465
1999
|
// like every other member, does not write them (unless the admin key is a
|
|
1466
2000
|
// device of the owner, checked above).
|
|
@@ -1559,10 +2093,17 @@ class BaseMindooTenantDirectory {
|
|
|
1559
2093
|
if (!lookup)
|
|
1560
2094
|
return [];
|
|
1561
2095
|
const grants = await this.findGrantAccessDocuments(lookup.username);
|
|
1562
|
-
|
|
2096
|
+
const live = grants
|
|
1563
2097
|
.filter((doc) => !doc.isDeleted())
|
|
1564
|
-
.map((doc) => doc.getData())
|
|
1565
|
-
|
|
2098
|
+
.map((doc) => doc.getData());
|
|
2099
|
+
const listed = live.filter((data) => (0, grantKeys_1.extractActiveKeyPairs)(data).some((pair) => (0, DirectoryStateNode_1.signingKeysEqual)(pair.signingPublicKey, signingKey)));
|
|
2100
|
+
if (listed.length > 0)
|
|
2101
|
+
return listed;
|
|
2102
|
+
// A certified device acts under its person's grants (userkeys.md §9.6).
|
|
2103
|
+
const certified = await this.resolveSignerToGrant(signingKey, Number.MAX_SAFE_INTEGER);
|
|
2104
|
+
if (certified?.via !== "certificate")
|
|
2105
|
+
return [];
|
|
2106
|
+
return live.filter((data) => data.username_hash === certified.grant.usernameHash);
|
|
1566
2107
|
}
|
|
1567
2108
|
/**
|
|
1568
2109
|
* Let an identity deposit into personal databases with these prefixes
|
|
@@ -1597,10 +2138,7 @@ class BaseMindooTenantDirectory {
|
|
|
1597
2138
|
if (grants.length === 0)
|
|
1598
2139
|
throw new Error(`No grant for ${username}`);
|
|
1599
2140
|
const directoryDB = await this.getDirectoryDB();
|
|
1600
|
-
const signingKeyPair =
|
|
1601
|
-
publicKey: this.tenant.getAdministrationPublicKey(),
|
|
1602
|
-
privateKey: administrationPrivateKey,
|
|
1603
|
-
};
|
|
2141
|
+
const signingKeyPair = await this.adminKeyPair(administrationPrivateKey, administrationPrivateKeyPassword);
|
|
1604
2142
|
for (const grant of grants) {
|
|
1605
2143
|
await directoryDB.changeDoc(grant, (d) => {
|
|
1606
2144
|
const data = d.getData();
|
|
@@ -1649,8 +2187,7 @@ class BaseMindooTenantDirectory {
|
|
|
1649
2187
|
if (dbid === types_1.DIRECTORY_DB_ID || dbid === types_1.USER_DIRECTORY_DB_ID)
|
|
1650
2188
|
return true;
|
|
1651
2189
|
const currentUser = await this.tenant.getCurrentUserId();
|
|
1652
|
-
|
|
1653
|
-
if (currentUser.userSigningPublicKey === adminKey)
|
|
2190
|
+
if (await this.isAdminKeyNow(currentUser.userSigningPublicKey))
|
|
1654
2191
|
return true;
|
|
1655
2192
|
// The personal-database invariant (§6.11) decides first, policy on top.
|
|
1656
2193
|
const personal = await this.evaluatePersonalDatabaseAccess({ dbid, signingKey: currentUser.userSigningPublicKey });
|
|
@@ -1707,10 +2244,7 @@ class BaseMindooTenantDirectory {
|
|
|
1707
2244
|
async writePolicyDoc(docId, policy, administrationPrivateKey, administrationPrivateKeyPassword) {
|
|
1708
2245
|
const baseTenant = this.tenant;
|
|
1709
2246
|
const directoryDB = await this.getDirectoryDB();
|
|
1710
|
-
const adminSigningKeyPair =
|
|
1711
|
-
publicKey: baseTenant.getAdministrationPublicKey(),
|
|
1712
|
-
privateKey: administrationPrivateKey,
|
|
1713
|
-
};
|
|
2247
|
+
const adminSigningKeyPair = await this.adminKeyPair(administrationPrivateKey, administrationPrivateKeyPassword);
|
|
1714
2248
|
const doc = await this.getOrCreateAclDoc(directoryDB, docId, adminSigningKeyPair, administrationPrivateKeyPassword);
|
|
1715
2249
|
await directoryDB.changeDoc(doc, async (d) => {
|
|
1716
2250
|
const data = d.getData();
|
|
@@ -1756,10 +2290,7 @@ class BaseMindooTenantDirectory {
|
|
|
1756
2290
|
(0, types_3.validateAclRule)(ruleDoc);
|
|
1757
2291
|
const baseTenant = this.tenant;
|
|
1758
2292
|
const directoryDB = await this.getDirectoryDB();
|
|
1759
|
-
const adminSigningKeyPair =
|
|
1760
|
-
publicKey: baseTenant.getAdministrationPublicKey(),
|
|
1761
|
-
privateKey: administrationPrivateKey,
|
|
1762
|
-
};
|
|
2293
|
+
const adminSigningKeyPair = await this.adminKeyPair(administrationPrivateKey, administrationPrivateKeyPassword);
|
|
1763
2294
|
const doc = await this.getOrCreateAclDoc(directoryDB, (0, types_3.aclRuleDocId)(rule.ruleId), adminSigningKeyPair, administrationPrivateKeyPassword);
|
|
1764
2295
|
await directoryDB.changeDoc(doc, async (d) => {
|
|
1765
2296
|
const data = d.getData();
|
|
@@ -1811,10 +2342,7 @@ class BaseMindooTenantDirectory {
|
|
|
1811
2342
|
async deleteRule(ruleId, administrationPrivateKey, administrationPrivateKeyPassword) {
|
|
1812
2343
|
const baseTenant = this.tenant;
|
|
1813
2344
|
const directoryDB = await this.getDirectoryDB();
|
|
1814
|
-
const adminSigningKeyPair =
|
|
1815
|
-
publicKey: baseTenant.getAdministrationPublicKey(),
|
|
1816
|
-
privateKey: administrationPrivateKey,
|
|
1817
|
-
};
|
|
2345
|
+
const adminSigningKeyPair = await this.adminKeyPair(administrationPrivateKey, administrationPrivateKeyPassword);
|
|
1818
2346
|
try {
|
|
1819
2347
|
await directoryDB.deleteDocument((0, types_3.aclRuleDocId)(ruleId), {
|
|
1820
2348
|
signingKeyPair: adminSigningKeyPair,
|
|
@@ -1833,10 +2361,9 @@ class BaseMindooTenantDirectory {
|
|
|
1833
2361
|
async addTrustedWitness(witness, administrationPrivateKey, administrationPrivateKeyPassword) {
|
|
1834
2362
|
const baseTenant = this.tenant;
|
|
1835
2363
|
const directoryDB = await this.getDirectoryDB();
|
|
1836
|
-
const adminSigningKeyPair =
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
};
|
|
2364
|
+
const adminSigningKeyPair = await this.adminKeyPair(administrationPrivateKey, administrationPrivateKeyPassword);
|
|
2365
|
+
// Trusted witnesses decide whose receipt times count (§6.13): root only.
|
|
2366
|
+
await this.rootKeyPair(administrationPrivateKey, administrationPrivateKeyPassword, "add trusted witnesses");
|
|
1840
2367
|
const fingerprint = await this.sha256Hex(witness.witnessPublicKey);
|
|
1841
2368
|
const doc = await this.getOrCreateAclDoc(directoryDB, (0, types_2.aclTrustedWitnessDocId)(fingerprint), adminSigningKeyPair, administrationPrivateKeyPassword);
|
|
1842
2369
|
await directoryDB.changeDoc(doc, async (d) => {
|
|
@@ -1853,10 +2380,8 @@ class BaseMindooTenantDirectory {
|
|
|
1853
2380
|
async removeTrustedWitness(witnessPublicKey, administrationPrivateKey, administrationPrivateKeyPassword) {
|
|
1854
2381
|
const baseTenant = this.tenant;
|
|
1855
2382
|
const directoryDB = await this.getDirectoryDB();
|
|
1856
|
-
const adminSigningKeyPair =
|
|
1857
|
-
|
|
1858
|
-
privateKey: administrationPrivateKey,
|
|
1859
|
-
};
|
|
2383
|
+
const adminSigningKeyPair = await this.adminKeyPair(administrationPrivateKey, administrationPrivateKeyPassword);
|
|
2384
|
+
await this.rootKeyPair(administrationPrivateKey, administrationPrivateKeyPassword, "remove trusted witnesses");
|
|
1860
2385
|
const fingerprint = await this.sha256Hex(witnessPublicKey);
|
|
1861
2386
|
try {
|
|
1862
2387
|
await directoryDB.deleteDocument((0, types_2.aclTrustedWitnessDocId)(fingerprint), {
|
|
@@ -2076,6 +2601,15 @@ class BaseMindooTenantDirectory {
|
|
|
2076
2601
|
? await this.encryptUserDetailsForTenant(this.buildUserDetailsPayload(username, changes.details))
|
|
2077
2602
|
: null;
|
|
2078
2603
|
const identityHashes = await this.computeIdentityHashes(username);
|
|
2604
|
+
// Devices this person certified (userkeys.md §9.6) can be revoked and
|
|
2605
|
+
// wiped here like listed ones, although they are not on the grant.
|
|
2606
|
+
const certifiedByHash = new Map();
|
|
2607
|
+
for (const grant of await this.findGrantAccessDocuments(username)) {
|
|
2608
|
+
const hash = grant.getData().username_hash;
|
|
2609
|
+
if (typeof hash === "string" && !certifiedByHash.has(hash)) {
|
|
2610
|
+
certifiedByHash.set(hash, await this.certificatesOfPerson(hash));
|
|
2611
|
+
}
|
|
2612
|
+
}
|
|
2079
2613
|
await this.editGrantArrays(username, administrationPrivateKey, administrationPrivateKeyPassword, (data) => {
|
|
2080
2614
|
if (userDetailsEncrypted !== null) {
|
|
2081
2615
|
data.user_details_encrypted = userDetailsEncrypted;
|
|
@@ -2107,12 +2641,52 @@ class BaseMindooTenantDirectory {
|
|
|
2107
2641
|
}
|
|
2108
2642
|
return p;
|
|
2109
2643
|
});
|
|
2110
|
-
|
|
2644
|
+
const certified = typeof data.username_hash === "string" ? certifiedByHash.get(data.username_hash) ?? [] : [];
|
|
2645
|
+
const isCertifiedOnly = (key) => certified.some((c) => (0, DirectoryStateNode_1.signingKeysEqual)(c.deviceSigningPublicKey, key)) &&
|
|
2646
|
+
!(0, grantKeys_1.extractActiveKeyPairs)(data).some((pair) => (0, DirectoryStateNode_1.signingKeysEqual)(pair.signingPublicKey, key));
|
|
2647
|
+
for (const cert of certified) {
|
|
2648
|
+
const key = cert.deviceSigningPublicKey;
|
|
2649
|
+
if (!revokeSet.has(key) || next.some((p) => (0, DirectoryStateNode_1.signingKeysEqual)(p.signingPublicKey, key)))
|
|
2650
|
+
continue;
|
|
2651
|
+
next.push({
|
|
2652
|
+
signingPublicKey: key,
|
|
2653
|
+
encryptionPublicKey: cert.deviceEncryptionPublicKey,
|
|
2654
|
+
...(cert.label ? { label: cert.label } : {}),
|
|
2655
|
+
revoked: true,
|
|
2656
|
+
revokedAt: now,
|
|
2657
|
+
});
|
|
2658
|
+
}
|
|
2659
|
+
// Restoring a certified device only lifts the revocation; it does not
|
|
2660
|
+
// turn it into a device listed on the grant.
|
|
2661
|
+
const kept = next.filter((p) => !(restoreSet.has(p.signingPublicKey) && isCertifiedOnly(p.signingPublicKey) && !p.revoked));
|
|
2662
|
+
(0, grantKeys_1.applyKeyPairFields)(data, kept);
|
|
2111
2663
|
// Remote-wipe set: authoritative when supplied (else preserve existing),
|
|
2112
|
-
// restricted to keys present on this grant doc
|
|
2113
|
-
|
|
2664
|
+
// restricted to keys present on this grant doc or certified by its
|
|
2665
|
+
// person, and never for restored keys.
|
|
2666
|
+
const present = new Set([
|
|
2667
|
+
...kept.map((p) => p.signingPublicKey),
|
|
2668
|
+
...certified.map((c) => c.deviceSigningPublicKey),
|
|
2669
|
+
]);
|
|
2114
2670
|
const desiredWipe = changes.wipeSigningKeys ?? (0, grantKeys_1.extractWipeRequestedSigningKeys)(data);
|
|
2115
2671
|
data.wipeRequestedForSigningKeys = Array.from(new Set(desiredWipe.filter((k) => present.has(k) && !restoreSet.has(k))));
|
|
2672
|
+
if (changes.deviceSelfGrant !== undefined) {
|
|
2673
|
+
const existing = (0, grantKeys_1.extractDeviceSelfGrant)(data);
|
|
2674
|
+
if (changes.deviceSelfGrant === null) {
|
|
2675
|
+
if (existing && existing.revokedAt === undefined) {
|
|
2676
|
+
data.deviceSelfGrant = { ...existing, revokedAt: now };
|
|
2677
|
+
}
|
|
2678
|
+
}
|
|
2679
|
+
else {
|
|
2680
|
+
const key = changes.deviceSelfGrant.trim();
|
|
2681
|
+
if (!key)
|
|
2682
|
+
throw new Error("deviceSelfGrant must be a PEM public key or null");
|
|
2683
|
+
if (!existing ||
|
|
2684
|
+
existing.revokedAt !== undefined ||
|
|
2685
|
+
existing.signingPublicKey.replace(/\s+/g, "") !== key.replace(/\s+/g, "")) {
|
|
2686
|
+
data.deviceSelfGrant = { signingPublicKey: key, grantedAt: now };
|
|
2687
|
+
}
|
|
2688
|
+
}
|
|
2689
|
+
}
|
|
2116
2690
|
});
|
|
2117
2691
|
}
|
|
2118
2692
|
/**
|
|
@@ -2398,10 +2972,7 @@ class BaseMindooTenantDirectory {
|
|
|
2398
2972
|
const docId = (0, types_3.aclKeyDistributionDocId)(keyId);
|
|
2399
2973
|
const baseTenant = this.tenant;
|
|
2400
2974
|
const directoryDB = await this.getDirectoryDB();
|
|
2401
|
-
const adminSigningKeyPair =
|
|
2402
|
-
publicKey: baseTenant.getAdministrationPublicKey(),
|
|
2403
|
-
privateKey: administrationPrivateKey,
|
|
2404
|
-
};
|
|
2975
|
+
const adminSigningKeyPair = await this.adminKeyPair(administrationPrivateKey, administrationPrivateKeyPassword);
|
|
2405
2976
|
const doc = await this.getOrCreateAclDoc(directoryDB, docId, adminSigningKeyPair, administrationPrivateKeyPassword);
|
|
2406
2977
|
await directoryDB.changeDoc(doc, async (d) => {
|
|
2407
2978
|
const data = d.getData();
|
|
@@ -2518,10 +3089,7 @@ class BaseMindooTenantDirectory {
|
|
|
2518
3089
|
async deleteKeyDistribution(keyId, administrationPrivateKey, administrationPrivateKeyPassword) {
|
|
2519
3090
|
const baseTenant = this.tenant;
|
|
2520
3091
|
const directoryDB = await this.getDirectoryDB();
|
|
2521
|
-
const adminSigningKeyPair =
|
|
2522
|
-
publicKey: baseTenant.getAdministrationPublicKey(),
|
|
2523
|
-
privateKey: administrationPrivateKey,
|
|
2524
|
-
};
|
|
3092
|
+
const adminSigningKeyPair = await this.adminKeyPair(administrationPrivateKey, administrationPrivateKeyPassword);
|
|
2525
3093
|
try {
|
|
2526
3094
|
await directoryDB.deleteDocument((0, types_3.aclKeyDistributionDocId)(keyId), {
|
|
2527
3095
|
signingKeyPair: adminSigningKeyPair,
|
|
@@ -2778,10 +3346,7 @@ class BaseMindooTenantDirectory {
|
|
|
2778
3346
|
const docId = (0, types_3.aclAppDistributionDocId)(appId);
|
|
2779
3347
|
const baseTenant = this.tenant;
|
|
2780
3348
|
const directoryDB = await this.getDirectoryDB();
|
|
2781
|
-
const adminSigningKeyPair =
|
|
2782
|
-
publicKey: baseTenant.getAdministrationPublicKey(),
|
|
2783
|
-
privateKey: administrationPrivateKey,
|
|
2784
|
-
};
|
|
3349
|
+
const adminSigningKeyPair = await this.adminKeyPair(administrationPrivateKey, administrationPrivateKeyPassword);
|
|
2785
3350
|
const doc = await this.getOrCreateAclDoc(directoryDB, docId, adminSigningKeyPair, administrationPrivateKeyPassword);
|
|
2786
3351
|
await directoryDB.changeDoc(doc, async (d) => {
|
|
2787
3352
|
const data = d.getData();
|
|
@@ -2890,10 +3455,7 @@ class BaseMindooTenantDirectory {
|
|
|
2890
3455
|
async deleteAppDistribution(appId, administrationPrivateKey, administrationPrivateKeyPassword) {
|
|
2891
3456
|
const baseTenant = this.tenant;
|
|
2892
3457
|
const directoryDB = await this.getDirectoryDB();
|
|
2893
|
-
const adminSigningKeyPair =
|
|
2894
|
-
publicKey: baseTenant.getAdministrationPublicKey(),
|
|
2895
|
-
privateKey: administrationPrivateKey,
|
|
2896
|
-
};
|
|
3458
|
+
const adminSigningKeyPair = await this.adminKeyPair(administrationPrivateKey, administrationPrivateKeyPassword);
|
|
2897
3459
|
try {
|
|
2898
3460
|
await directoryDB.deleteDocument((0, types_3.aclAppDistributionDocId)(appId), {
|
|
2899
3461
|
signingKeyPair: adminSigningKeyPair,
|
|
@@ -3017,10 +3579,7 @@ class BaseMindooTenantDirectory {
|
|
|
3017
3579
|
const docId = (0, types_3.aclSyncSetupPolicyDocId)(policyId);
|
|
3018
3580
|
const baseTenant = this.tenant;
|
|
3019
3581
|
const directoryDB = await this.getDirectoryDB();
|
|
3020
|
-
const adminSigningKeyPair =
|
|
3021
|
-
publicKey: baseTenant.getAdministrationPublicKey(),
|
|
3022
|
-
privateKey: administrationPrivateKey,
|
|
3023
|
-
};
|
|
3582
|
+
const adminSigningKeyPair = await this.adminKeyPair(administrationPrivateKey, administrationPrivateKeyPassword);
|
|
3024
3583
|
const doc = await this.getOrCreateAclDoc(directoryDB, docId, adminSigningKeyPair, administrationPrivateKeyPassword);
|
|
3025
3584
|
await directoryDB.changeDoc(doc, async (d) => {
|
|
3026
3585
|
const data = d.getData();
|
|
@@ -3130,10 +3689,7 @@ class BaseMindooTenantDirectory {
|
|
|
3130
3689
|
async deleteSyncSetupPolicy(policyId, administrationPrivateKey, administrationPrivateKeyPassword) {
|
|
3131
3690
|
const baseTenant = this.tenant;
|
|
3132
3691
|
const directoryDB = await this.getDirectoryDB();
|
|
3133
|
-
const adminSigningKeyPair =
|
|
3134
|
-
publicKey: baseTenant.getAdministrationPublicKey(),
|
|
3135
|
-
privateKey: administrationPrivateKey,
|
|
3136
|
-
};
|
|
3692
|
+
const adminSigningKeyPair = await this.adminKeyPair(administrationPrivateKey, administrationPrivateKeyPassword);
|
|
3137
3693
|
try {
|
|
3138
3694
|
await directoryDB.deleteDocument((0, types_3.aclSyncSetupPolicyDocId)(policyId), {
|
|
3139
3695
|
signingKeyPair: adminSigningKeyPair,
|
|
@@ -3212,7 +3768,7 @@ class BaseMindooTenantDirectory {
|
|
|
3212
3768
|
* server-readable and carries every variant.
|
|
3213
3769
|
*/
|
|
3214
3770
|
async getRevokedDecryptionKeyIdsForSigningKey(signingKey) {
|
|
3215
|
-
if (
|
|
3771
|
+
if (await this.isAdminKeyNow(signingKey))
|
|
3216
3772
|
return [];
|
|
3217
3773
|
const lookup = await this.getUserBySigningPublicKey(signingKey);
|
|
3218
3774
|
if (!lookup)
|
|
@@ -3290,10 +3846,7 @@ class BaseMindooTenantDirectory {
|
|
|
3290
3846
|
}
|
|
3291
3847
|
const baseTenant = this.tenant;
|
|
3292
3848
|
const directoryDB = await this.getDirectoryDB();
|
|
3293
|
-
const adminSigningKeyPair =
|
|
3294
|
-
publicKey: baseTenant.getAdministrationPublicKey(),
|
|
3295
|
-
privateKey: administrationPrivateKey,
|
|
3296
|
-
};
|
|
3849
|
+
const adminSigningKeyPair = await this.adminKeyPair(administrationPrivateKey, administrationPrivateKeyPassword);
|
|
3297
3850
|
for (const grant of grants) {
|
|
3298
3851
|
const doc = await directoryDB.getDocument(grant.getId());
|
|
3299
3852
|
await directoryDB.changeDoc(doc, async (d) => {
|
|
@@ -3325,9 +3878,9 @@ class BaseMindooTenantDirectory {
|
|
|
3325
3878
|
*/
|
|
3326
3879
|
async identitySetForSigningKey(signingKey, node, opts) {
|
|
3327
3880
|
// The tenant admin key is the root of trust and is resolved even without a
|
|
3328
|
-
// grant document (it authors the directory itself).
|
|
3329
|
-
|
|
3330
|
-
const isAdmin = signingKey
|
|
3881
|
+
// grant document (it authors the directory itself). Delegated admin keys
|
|
3882
|
+
// (§6.13) count while their window covers the evaluation time.
|
|
3883
|
+
const isAdmin = (0, DirectoryStateNode_1.isAdminInNode)(node, this.getRootAdminPublicKey(), signingKey, (0, adminKeys_1.adminDecisionTime)(opts.trustedTime ?? Number.MAX_SAFE_INTEGER, (0, timeSource_1.semanticNow)()));
|
|
3331
3884
|
// Audit #4 (revocation lag): resolve the acting identity strictly from the
|
|
3332
3885
|
// directory state at the entry's trusted time `T`. A signing key that was
|
|
3333
3886
|
// not an ACTIVE grant at `T` carries only the pseudo-tokens
|
|
@@ -3337,7 +3890,8 @@ class BaseMindooTenantDirectory {
|
|
|
3337
3890
|
// allow-rules until the trust cache refreshed (up to
|
|
3338
3891
|
// DIRECTORY_SYNC_INTERVAL_MS later). The `node` is the time-`T` snapshot
|
|
3339
3892
|
// whose `bySigningKey` only contains keys whose grant is active at `T`.
|
|
3340
|
-
|
|
3893
|
+
// A certified device (userkeys.md §9.6) acts for its person like a listed one.
|
|
3894
|
+
const grantedAtT = !!(await this.resolveSignerInNode(node, signingKey, opts.trustedTime ?? node.trustedTimeUpperBound));
|
|
3341
3895
|
// Resolve the author's username from their signing key. Trusted non-user
|
|
3342
3896
|
// keys (e.g. server-to-server sync identities) have no grant; treat them as
|
|
3343
3897
|
// an empty identity that still carries `$everyone`/`$author`. Keys that were
|
|
@@ -3560,6 +4114,30 @@ class BaseMindooTenantDirectory {
|
|
|
3560
4114
|
if (key.replace(/\s+/g, "") === needle)
|
|
3561
4115
|
return lookup;
|
|
3562
4116
|
}
|
|
4117
|
+
// A device the person certified (userkeys.md §9.6). Like the cache above,
|
|
4118
|
+
// this keeps naming the person after the device was revoked; callers that
|
|
4119
|
+
// need "active" check the signing-key universe or resolveSignerToGrant.
|
|
4120
|
+
const certified = await this.certifiedGrantInNode(await this.getDirectoryStateHead(), publicKey, Number.MAX_SAFE_INTEGER, { enforceRevocation: false });
|
|
4121
|
+
if (!certified)
|
|
4122
|
+
return null;
|
|
4123
|
+
const personKeys = [
|
|
4124
|
+
...certified.grant.signingKeys,
|
|
4125
|
+
...(certified.grant.revokedSigningKeys ?? []),
|
|
4126
|
+
];
|
|
4127
|
+
for (const personKey of personKeys) {
|
|
4128
|
+
const lookup = this.userLookupCache.get(personKey) ??
|
|
4129
|
+
[...this.userLookupCache.entries()].find(([k]) => (0, DirectoryStateNode_1.signingKeysEqual)(k, personKey))?.[1];
|
|
4130
|
+
if (lookup) {
|
|
4131
|
+
// Device-specific fields come from the certificate; a join response
|
|
4132
|
+
// wrapped for another device of the person must not be handed out.
|
|
4133
|
+
const { joinResponseWrapped: _other, ...personLookup } = lookup;
|
|
4134
|
+
return {
|
|
4135
|
+
...personLookup,
|
|
4136
|
+
signingPublicKey: certified.cert.deviceSigningPublicKey,
|
|
4137
|
+
encryptionPublicKey: certified.cert.deviceEncryptionPublicKey,
|
|
4138
|
+
};
|
|
4139
|
+
}
|
|
4140
|
+
}
|
|
3563
4141
|
return null;
|
|
3564
4142
|
}
|
|
3565
4143
|
/**
|
|
@@ -3577,14 +4155,27 @@ class BaseMindooTenantDirectory {
|
|
|
3577
4155
|
const grants = await this.findGrantAccessDocuments(username);
|
|
3578
4156
|
const active = new Set();
|
|
3579
4157
|
const wipeRequested = new Set();
|
|
4158
|
+
const revokedPairs = new Set();
|
|
4159
|
+
const hashes = new Set();
|
|
3580
4160
|
for (const grant of grants) {
|
|
3581
4161
|
const data = grant.getData();
|
|
3582
4162
|
for (const key of (0, grantKeys_1.extractSigningPublicKeys)(data))
|
|
3583
4163
|
active.add(key);
|
|
3584
4164
|
for (const key of (0, grantKeys_1.extractWipeRequestedSigningKeys)(data))
|
|
3585
4165
|
wipeRequested.add(key);
|
|
4166
|
+
for (const pair of (0, grantKeys_1.extractRevokedKeyPairs)(data))
|
|
4167
|
+
revokedPairs.add(pair.signingPublicKey);
|
|
4168
|
+
if (typeof data.username_hash === "string")
|
|
4169
|
+
hashes.add(data.username_hash);
|
|
3586
4170
|
}
|
|
3587
|
-
|
|
4171
|
+
// Devices the person certified count as active (userkeys.md §9.6).
|
|
4172
|
+
for (const hash of hashes) {
|
|
4173
|
+
for (const cert of await this.listCertifiedDevices(hash))
|
|
4174
|
+
active.add(cert.deviceSigningPublicKey);
|
|
4175
|
+
}
|
|
4176
|
+
// A key revoked on one grant but active on another (or restored) is active.
|
|
4177
|
+
const revoked = [...revokedPairs].filter((key) => ![...active].some((other) => (0, DirectoryStateNode_1.signingKeysEqual)(other, key)));
|
|
4178
|
+
return { active: Array.from(active), wipeRequested: Array.from(wipeRequested), revoked };
|
|
3588
4179
|
}
|
|
3589
4180
|
/**
|
|
3590
4181
|
* List a user's currently-granted device key pairs (§6.5), each with its
|
|
@@ -3666,10 +4257,7 @@ class BaseMindooTenantDirectory {
|
|
|
3666
4257
|
const requestedAt = (0, timeSource_1.semanticNow)();
|
|
3667
4258
|
const baseTenant = this.tenant;
|
|
3668
4259
|
const directoryDB = await this.getDirectoryDB();
|
|
3669
|
-
const adminSigningKeyPair =
|
|
3670
|
-
publicKey: baseTenant.getAdministrationPublicKey(),
|
|
3671
|
-
privateKey: administrationPrivateKey,
|
|
3672
|
-
};
|
|
4260
|
+
const adminSigningKeyPair = await this.adminKeyPair(administrationPrivateKey, administrationPrivateKeyPassword);
|
|
3673
4261
|
const doc = await this.getOrCreateAclDoc(directoryDB, docId, adminSigningKeyPair, administrationPrivateKeyPassword);
|
|
3674
4262
|
await directoryDB.changeDoc(doc, async (d) => {
|
|
3675
4263
|
const data = d.getData();
|
|
@@ -3748,10 +4336,7 @@ class BaseMindooTenantDirectory {
|
|
|
3748
4336
|
async deleteDocHistoryPurge(requestId, administrationPrivateKey, administrationPrivateKeyPassword) {
|
|
3749
4337
|
const baseTenant = this.tenant;
|
|
3750
4338
|
const directoryDB = await this.getDirectoryDB();
|
|
3751
|
-
const adminSigningKeyPair =
|
|
3752
|
-
publicKey: baseTenant.getAdministrationPublicKey(),
|
|
3753
|
-
privateKey: administrationPrivateKey,
|
|
3754
|
-
};
|
|
4339
|
+
const adminSigningKeyPair = await this.adminKeyPair(administrationPrivateKey, administrationPrivateKeyPassword);
|
|
3755
4340
|
try {
|
|
3756
4341
|
await directoryDB.deleteDocument((0, types_3.aclDocHistoryPurgeDocId)(requestId), {
|
|
3757
4342
|
signingKeyPair: adminSigningKeyPair,
|
|
@@ -3800,10 +4385,7 @@ class BaseMindooTenantDirectory {
|
|
|
3800
4385
|
async changeTenantSettings(changeFunc, administrationPrivateKey, administrationPrivateKeyPassword) {
|
|
3801
4386
|
const baseTenant = this.tenant;
|
|
3802
4387
|
const directoryDB = await this.getDirectoryDB();
|
|
3803
|
-
const adminSigningKeyPair =
|
|
3804
|
-
publicKey: baseTenant.getAdministrationPublicKey(),
|
|
3805
|
-
privateKey: administrationPrivateKey,
|
|
3806
|
-
};
|
|
4388
|
+
const adminSigningKeyPair = await this.adminKeyPair(administrationPrivateKey, administrationPrivateKeyPassword);
|
|
3807
4389
|
// Get cached settings doc or create new one
|
|
3808
4390
|
let settingsDoc = await this.getTenantSettings();
|
|
3809
4391
|
if (!settingsDoc) {
|
|
@@ -3827,10 +4409,7 @@ class BaseMindooTenantDirectory {
|
|
|
3827
4409
|
async changeDBSettings(dbId, changeFunc, administrationPrivateKey, administrationPrivateKeyPassword) {
|
|
3828
4410
|
const baseTenant = this.tenant;
|
|
3829
4411
|
const directoryDB = await this.getDirectoryDB();
|
|
3830
|
-
const adminSigningKeyPair =
|
|
3831
|
-
publicKey: baseTenant.getAdministrationPublicKey(),
|
|
3832
|
-
privateKey: administrationPrivateKey,
|
|
3833
|
-
};
|
|
4412
|
+
const adminSigningKeyPair = await this.adminKeyPair(administrationPrivateKey, administrationPrivateKeyPassword);
|
|
3834
4413
|
// Get cached settings doc for this dbId or create new one
|
|
3835
4414
|
let settingsDoc = await this.getDBSettings(dbId);
|
|
3836
4415
|
if (!settingsDoc) {
|
|
@@ -3885,10 +4464,7 @@ class BaseMindooTenantDirectory {
|
|
|
3885
4464
|
const normalizedGroupName = this.normalizeGroupName(groupName);
|
|
3886
4465
|
const baseTenant = this.tenant;
|
|
3887
4466
|
const directoryDB = await this.getDirectoryDB();
|
|
3888
|
-
const adminSigningKeyPair =
|
|
3889
|
-
publicKey: baseTenant.getAdministrationPublicKey(),
|
|
3890
|
-
privateKey: administrationPrivateKey,
|
|
3891
|
-
};
|
|
4467
|
+
const adminSigningKeyPair = await this.adminKeyPair(administrationPrivateKey, administrationPrivateKeyPassword);
|
|
3892
4468
|
// Compute hashes (salted v2 for new writes) and encrypted values for all
|
|
3893
4469
|
// new usernames. Reads match both v1 and v2 forms (docs/accesscontrol.md §6.5).
|
|
3894
4470
|
const newMembersHashes = await Promise.all(username.map(u => this.hashUsernameForWrite(u)));
|
|
@@ -3958,10 +4534,7 @@ class BaseMindooTenantDirectory {
|
|
|
3958
4534
|
// Group doesn't exist, nothing to do
|
|
3959
4535
|
return;
|
|
3960
4536
|
}
|
|
3961
|
-
const adminSigningKeyPair =
|
|
3962
|
-
publicKey: baseTenant.getAdministrationPublicKey(),
|
|
3963
|
-
privateKey: administrationPrivateKey,
|
|
3964
|
-
};
|
|
4537
|
+
const adminSigningKeyPair = await this.adminKeyPair(administrationPrivateKey, administrationPrivateKeyPassword);
|
|
3965
4538
|
// Compute hashes of usernames to remove. Include both legacy (v1) and
|
|
3966
4539
|
// salted (v2) forms so members stored under either scheme are removed.
|
|
3967
4540
|
const hashesToRemove = new Set((await Promise.all(username.map(u => this.usernameHashCandidates(u)))).flat());
|
|
@@ -4020,10 +4593,7 @@ class BaseMindooTenantDirectory {
|
|
|
4020
4593
|
// Group doesn't exist, nothing to do
|
|
4021
4594
|
return;
|
|
4022
4595
|
}
|
|
4023
|
-
const adminSigningKeyPair =
|
|
4024
|
-
publicKey: baseTenant.getAdministrationPublicKey(),
|
|
4025
|
-
privateKey: administrationPrivateKey,
|
|
4026
|
-
};
|
|
4596
|
+
const adminSigningKeyPair = await this.adminKeyPair(administrationPrivateKey, administrationPrivateKeyPassword);
|
|
4027
4597
|
// Delete the document using the proper API
|
|
4028
4598
|
await directoryDB.deleteDocument(cachedGroup.docId, {
|
|
4029
4599
|
signingKeyPair: adminSigningKeyPair,
|