mindoodb 0.0.94 → 0.0.95
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 +37 -0
- package/dist/core/BaseMindooDB.d.ts +9 -0
- package/dist/core/BaseMindooDB.d.ts.map +1 -1
- package/dist/core/BaseMindooDB.js +65 -2
- package/dist/core/BaseMindooDB.js.map +1 -1
- package/dist/core/BaseMindooTenant.d.ts +75 -1
- package/dist/core/BaseMindooTenant.d.ts.map +1 -1
- package/dist/core/BaseMindooTenant.js +229 -7
- package/dist/core/BaseMindooTenant.js.map +1 -1
- package/dist/core/BaseMindooTenantDirectory.d.ts +165 -2
- package/dist/core/BaseMindooTenantDirectory.d.ts.map +1 -1
- package/dist/core/BaseMindooTenantDirectory.js +648 -112
- 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.map +1 -1
- package/dist/core/appendonlystores/network/AuthenticationService.js +8 -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/index.d.ts +4 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +26 -5
- package/dist/core/index.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/types.d.ts +182 -0
- 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/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 +96 -0
- 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,254 @@ 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
|
+
* Read every `dcert_*` certificate from the `userdirectory` store without
|
|
1526
|
+
* materializing that database (whose own trust checks call back into this
|
|
1527
|
+
* index). Only `doc_create` entries carry a certificate; deletes withdraw
|
|
1528
|
+
* it. Rebuilt when the store head moves, at most every two seconds for
|
|
1529
|
+
* stores that cannot report a head.
|
|
1530
|
+
*/
|
|
1531
|
+
async loadDeviceCertificateIndex() {
|
|
1532
|
+
if (this.buildingDeviceCertIndex) {
|
|
1533
|
+
// Re-entry while building: answer from what is known.
|
|
1534
|
+
return this.deviceCertIndex?.byKey ?? new Map();
|
|
1535
|
+
}
|
|
1536
|
+
const baseTenant = this.tenant;
|
|
1537
|
+
if (typeof baseTenant.getDocStoreForDb !== "function")
|
|
1538
|
+
return new Map();
|
|
1539
|
+
let store;
|
|
1540
|
+
try {
|
|
1541
|
+
store = baseTenant.getDocStoreForDb(types_1.USER_DIRECTORY_DB_ID);
|
|
1542
|
+
}
|
|
1543
|
+
catch {
|
|
1544
|
+
return new Map();
|
|
1545
|
+
}
|
|
1546
|
+
const head = await store.getStoreHead?.().catch(() => undefined);
|
|
1547
|
+
const stamp = head ? `${head.epoch}:${head.maxReceiptOrder}` : null;
|
|
1548
|
+
const cached = this.deviceCertIndex;
|
|
1549
|
+
if (cached && stamp !== null && cached.stamp === stamp)
|
|
1550
|
+
return cached.byKey;
|
|
1551
|
+
if (cached && stamp === null && Date.now() - cached.at < 2000)
|
|
1552
|
+
return cached.byKey;
|
|
1553
|
+
const build = (async () => {
|
|
1554
|
+
const byKey = new Map();
|
|
1555
|
+
const isCert = (m) => m.docId.startsWith(deviceCertificate_1.DEVICE_CERT_DOC_ID_PREFIX);
|
|
1556
|
+
const creates = (await store.findEntries("doc_create", null, null)).filter(isCert);
|
|
1557
|
+
if (creates.length === 0)
|
|
1558
|
+
return byKey;
|
|
1559
|
+
const deletes = (await store.findEntries("doc_delete", null, null)).filter(isCert);
|
|
1560
|
+
const entries = await store.getEntries(creates.map((m) => m.id));
|
|
1561
|
+
const crypto = this.tenant.getCryptoAdapter();
|
|
1562
|
+
for (const entry of entries) {
|
|
1563
|
+
try {
|
|
1564
|
+
const plaintext = await baseTenant.decryptPayload(entry.encryptedData, entry.decryptionKeyId);
|
|
1565
|
+
const cert = (0, deviceCertificate_1.asDeviceCertificate)((0, builtinDbInvariants_1.recordFromCreateChangeBytes)(plaintext));
|
|
1566
|
+
if (!cert || !(await (0, deviceCertificate_1.verifyDeviceCertificate)(crypto, cert)))
|
|
1567
|
+
continue;
|
|
1568
|
+
const createMeta = creates.find((m) => m.id === entry.id) ?? entry;
|
|
1569
|
+
const key = (0, fingerprint_1.normalizePublicKeyPem)(cert.deviceSigningPublicKey);
|
|
1570
|
+
const list = byKey.get(key) ?? [];
|
|
1571
|
+
list.push({
|
|
1572
|
+
cert,
|
|
1573
|
+
docId: entry.docId,
|
|
1574
|
+
createMeta,
|
|
1575
|
+
deleteMetas: deletes.filter((m) => m.docId === entry.docId),
|
|
1576
|
+
});
|
|
1577
|
+
byKey.set(key, list);
|
|
1578
|
+
}
|
|
1579
|
+
catch {
|
|
1580
|
+
// Not readable here (no $publicinfos) or not a certificate.
|
|
1581
|
+
}
|
|
1582
|
+
}
|
|
1583
|
+
return byKey;
|
|
1584
|
+
})();
|
|
1585
|
+
this.buildingDeviceCertIndex = build;
|
|
1586
|
+
try {
|
|
1587
|
+
const byKey = await build;
|
|
1588
|
+
this.deviceCertIndex = { stamp, at: Date.now(), byKey };
|
|
1589
|
+
return byKey;
|
|
1590
|
+
}
|
|
1591
|
+
finally {
|
|
1592
|
+
this.buildingDeviceCertIndex = null;
|
|
1593
|
+
}
|
|
1594
|
+
}
|
|
1595
|
+
/**
|
|
1596
|
+
* The person's device self-grant delegation at the head state
|
|
1597
|
+
* (docs/userkeys.md §9), or `null` when the admin never granted it.
|
|
1598
|
+
*/
|
|
1599
|
+
async getDeviceSelfGrantForUsernameHash(usernameHash) {
|
|
1600
|
+
const head = await this.getDirectoryStateHead();
|
|
1601
|
+
return head.usersByHash.get(usernameHash)?.deviceSelfGrant ?? null;
|
|
1602
|
+
}
|
|
1603
|
+
/** {@link getDeviceSelfGrantForUsernameHash} by username (newest grant wins). */
|
|
1604
|
+
async getDeviceSelfGrant(username) {
|
|
1605
|
+
const grants = await this.findGrantAccessDocuments(username);
|
|
1606
|
+
return (0, grantKeys_1.newestDeviceSelfGrant)(grants.map((g) => (0, grantKeys_1.extractDeviceSelfGrant)(g.getData())));
|
|
1607
|
+
}
|
|
1608
|
+
/**
|
|
1609
|
+
* Grant or revoke the person's right to certify their own devices
|
|
1610
|
+
* (docs/userkeys.md §9). `signingPublicKey` is the public half of their
|
|
1611
|
+
* device self-grant key (from the join request, or
|
|
1612
|
+
* `tenant.getDeviceSelfGrantPublicKey()` on one of their devices). `null`
|
|
1613
|
+
* revokes: the key stays on the grant with `revokedAt`, so certificates and
|
|
1614
|
+
* generations signed before stay valid; certificates issued later do not.
|
|
1615
|
+
*/
|
|
1616
|
+
async setDeviceSelfGrant(username, signingPublicKey, administrationPrivateKey, administrationPrivateKeyPassword) {
|
|
1617
|
+
await this.updateUserGrant(username, { deviceSelfGrant: signingPublicKey }, administrationPrivateKey, administrationPrivateKeyPassword);
|
|
1618
|
+
}
|
|
1619
|
+
/**
|
|
1620
|
+
* Trusted time from which new User-Key generations must be signed
|
|
1621
|
+
* (`requireUserKeyGenerationSignatureSince`, docs/userkeys.md §9.3), or
|
|
1622
|
+
* `undefined`. Read from the `acl_defaultpolicy` head regardless of the
|
|
1623
|
+
* master switch, like {@link getRequireMetadataSignatureSince}.
|
|
1624
|
+
*/
|
|
1625
|
+
async getRequireUserKeyGenerationSignatureSince() {
|
|
1626
|
+
const head = await this.getDirectoryStateHead();
|
|
1627
|
+
const since = head.defaultPolicy?.requireUserKeyGenerationSignatureSince;
|
|
1628
|
+
return typeof since === "number" && Number.isFinite(since) ? since : undefined;
|
|
1629
|
+
}
|
|
1630
|
+
/**
|
|
1631
|
+
* Require signed User-Key generations from now on (docs/userkeys.md §9.3).
|
|
1632
|
+
* Idempotent: an existing cutoff is kept. Run it once every device of the
|
|
1633
|
+
* tenant runs a client that signs generations; unsigned generations
|
|
1634
|
+
* introduced before stay valid.
|
|
1635
|
+
*/
|
|
1636
|
+
async requireUserKeyGenerationSignatures(administrationPrivateKey, administrationPrivateKeyPassword) {
|
|
1637
|
+
const head = await this.getDirectoryStateHead();
|
|
1638
|
+
const existing = head.defaultPolicy?.requireUserKeyGenerationSignatureSince;
|
|
1639
|
+
if (typeof existing === "number" && Number.isFinite(existing)) {
|
|
1640
|
+
return { since: existing, changed: false };
|
|
1641
|
+
}
|
|
1642
|
+
const since = (0, timeSource_1.semanticNow)();
|
|
1643
|
+
await this.setDefaultAccessPolicy(head.defaultPolicy
|
|
1644
|
+
? { requireUserKeyGenerationSignatureSince: since }
|
|
1645
|
+
: { disableAllAccessChecksAndPolicies: true, requireUserKeyGenerationSignatureSince: since }, administrationPrivateKey, administrationPrivateKeyPassword);
|
|
1646
|
+
return { since, changed: true };
|
|
1144
1647
|
}
|
|
1145
1648
|
/**
|
|
1146
1649
|
* Whether the active policy has any Tier 2 (`withfields`) content rule for
|
|
@@ -1242,8 +1745,7 @@ class BaseMindooTenantDirectory {
|
|
|
1242
1745
|
*/
|
|
1243
1746
|
async canUseLlm() {
|
|
1244
1747
|
const currentUser = await this.tenant.getCurrentUserId();
|
|
1245
|
-
|
|
1246
|
-
if (currentUser.userSigningPublicKey === adminKey) {
|
|
1748
|
+
if (await this.isAdminKeyNow(currentUser.userSigningPublicKey)) {
|
|
1247
1749
|
return { allowed: true, reason: "tenant admin", tier: "tier1" };
|
|
1248
1750
|
}
|
|
1249
1751
|
// Head selector, NOT Date.now() — see canDo.
|
|
@@ -1310,8 +1812,7 @@ class BaseMindooTenantDirectory {
|
|
|
1310
1812
|
if (allowedDbIds.includes(dbid))
|
|
1311
1813
|
return true;
|
|
1312
1814
|
if (opts?.signingKey !== undefined) {
|
|
1313
|
-
|
|
1314
|
-
if (opts.signingKey === adminKey)
|
|
1815
|
+
if (await this.isAdminKeyNow(opts.signingKey))
|
|
1315
1816
|
return true;
|
|
1316
1817
|
}
|
|
1317
1818
|
return false;
|
|
@@ -1335,9 +1836,8 @@ class BaseMindooTenantDirectory {
|
|
|
1335
1836
|
// policy that defines the read gate can be read at all).
|
|
1336
1837
|
if (input.dbid === types_1.DIRECTORY_DB_ID || input.dbid === types_1.USER_DIRECTORY_DB_ID)
|
|
1337
1838
|
return true;
|
|
1338
|
-
//
|
|
1339
|
-
|
|
1340
|
-
if (input.signingKey === adminKey)
|
|
1839
|
+
// Tenant admins (root and active delegated admins, §6.13) are exempt from the read gate.
|
|
1840
|
+
if (await this.isAdminKeyNow(input.signingKey))
|
|
1341
1841
|
return true;
|
|
1342
1842
|
// Gate 2 — per-user/group database read gate (`doc_read`, §6.6). Evaluated
|
|
1343
1843
|
// server-side (Tier 1) from the principal's signing key at acceptance time.
|
|
@@ -1363,8 +1863,7 @@ class BaseMindooTenantDirectory {
|
|
|
1363
1863
|
async evaluateReadScopeForSigningKey(input) {
|
|
1364
1864
|
if (input.dbid === types_1.DIRECTORY_DB_ID || input.dbid === types_1.USER_DIRECTORY_DB_ID)
|
|
1365
1865
|
return "all";
|
|
1366
|
-
|
|
1367
|
-
if (input.signingKey === adminKey)
|
|
1866
|
+
if (await this.isAdminKeyNow(input.signingKey))
|
|
1368
1867
|
return "all";
|
|
1369
1868
|
const decision = await this.evaluateAccessForSigningKey({
|
|
1370
1869
|
op: "doc_read_all",
|
|
@@ -1397,7 +1896,7 @@ class BaseMindooTenantDirectory {
|
|
|
1397
1896
|
if (personal) {
|
|
1398
1897
|
if (personal.mode === "none")
|
|
1399
1898
|
return personal;
|
|
1400
|
-
if (
|
|
1899
|
+
if (await this.isAdminKeyNow(input.signingKey)) {
|
|
1401
1900
|
return personal;
|
|
1402
1901
|
}
|
|
1403
1902
|
if (personal.mode === "read") {
|
|
@@ -1453,7 +1952,7 @@ class BaseMindooTenantDirectory {
|
|
|
1453
1952
|
const owner = (0, builtinDbInvariants_1.personalDatabaseOwner)(input.dbid, head.defaultPolicy?.personalDatabasePrefixes ?? []);
|
|
1454
1953
|
if (!owner)
|
|
1455
1954
|
return null;
|
|
1456
|
-
const isAdmin =
|
|
1955
|
+
const isAdmin = await this.isAdminKeyNow(input.signingKey);
|
|
1457
1956
|
if (isAdmin && owner.prefix !== "mbusy_") {
|
|
1458
1957
|
return { mode: "read", reason: "tenant admin" };
|
|
1459
1958
|
}
|
|
@@ -1461,6 +1960,12 @@ class BaseMindooTenantDirectory {
|
|
|
1461
1960
|
if (grant && (0, grantKeys_1.extractActiveKeyPairs)(grant).some((pair) => (0, DirectoryStateNode_1.signingKeysEqual)(pair.signingPublicKey, input.signingKey))) {
|
|
1462
1961
|
return { mode: "read", reason: "a device of the owner" };
|
|
1463
1962
|
}
|
|
1963
|
+
if (grant && typeof grant.username_hash === "string") {
|
|
1964
|
+
const certified = await this.resolveSignerInNode(head, input.signingKey, Number.MAX_SAFE_INTEGER);
|
|
1965
|
+
if (certified?.via === "certificate" && certified.grant.usernameHash === grant.username_hash) {
|
|
1966
|
+
return { mode: "read", reason: "a certified device of the owner" };
|
|
1967
|
+
}
|
|
1968
|
+
}
|
|
1464
1969
|
// Free/busy (§6.11): the tenant admin reads every person's busy times but,
|
|
1465
1970
|
// like every other member, does not write them (unless the admin key is a
|
|
1466
1971
|
// device of the owner, checked above).
|
|
@@ -1559,10 +2064,17 @@ class BaseMindooTenantDirectory {
|
|
|
1559
2064
|
if (!lookup)
|
|
1560
2065
|
return [];
|
|
1561
2066
|
const grants = await this.findGrantAccessDocuments(lookup.username);
|
|
1562
|
-
|
|
2067
|
+
const live = grants
|
|
1563
2068
|
.filter((doc) => !doc.isDeleted())
|
|
1564
|
-
.map((doc) => doc.getData())
|
|
1565
|
-
|
|
2069
|
+
.map((doc) => doc.getData());
|
|
2070
|
+
const listed = live.filter((data) => (0, grantKeys_1.extractActiveKeyPairs)(data).some((pair) => (0, DirectoryStateNode_1.signingKeysEqual)(pair.signingPublicKey, signingKey)));
|
|
2071
|
+
if (listed.length > 0)
|
|
2072
|
+
return listed;
|
|
2073
|
+
// A certified device acts under its person's grants (userkeys.md §9.6).
|
|
2074
|
+
const certified = await this.resolveSignerToGrant(signingKey, Number.MAX_SAFE_INTEGER);
|
|
2075
|
+
if (certified?.via !== "certificate")
|
|
2076
|
+
return [];
|
|
2077
|
+
return live.filter((data) => data.username_hash === certified.grant.usernameHash);
|
|
1566
2078
|
}
|
|
1567
2079
|
/**
|
|
1568
2080
|
* Let an identity deposit into personal databases with these prefixes
|
|
@@ -1597,10 +2109,7 @@ class BaseMindooTenantDirectory {
|
|
|
1597
2109
|
if (grants.length === 0)
|
|
1598
2110
|
throw new Error(`No grant for ${username}`);
|
|
1599
2111
|
const directoryDB = await this.getDirectoryDB();
|
|
1600
|
-
const signingKeyPair =
|
|
1601
|
-
publicKey: this.tenant.getAdministrationPublicKey(),
|
|
1602
|
-
privateKey: administrationPrivateKey,
|
|
1603
|
-
};
|
|
2112
|
+
const signingKeyPair = await this.adminKeyPair(administrationPrivateKey, administrationPrivateKeyPassword);
|
|
1604
2113
|
for (const grant of grants) {
|
|
1605
2114
|
await directoryDB.changeDoc(grant, (d) => {
|
|
1606
2115
|
const data = d.getData();
|
|
@@ -1649,8 +2158,7 @@ class BaseMindooTenantDirectory {
|
|
|
1649
2158
|
if (dbid === types_1.DIRECTORY_DB_ID || dbid === types_1.USER_DIRECTORY_DB_ID)
|
|
1650
2159
|
return true;
|
|
1651
2160
|
const currentUser = await this.tenant.getCurrentUserId();
|
|
1652
|
-
|
|
1653
|
-
if (currentUser.userSigningPublicKey === adminKey)
|
|
2161
|
+
if (await this.isAdminKeyNow(currentUser.userSigningPublicKey))
|
|
1654
2162
|
return true;
|
|
1655
2163
|
// The personal-database invariant (§6.11) decides first, policy on top.
|
|
1656
2164
|
const personal = await this.evaluatePersonalDatabaseAccess({ dbid, signingKey: currentUser.userSigningPublicKey });
|
|
@@ -1707,10 +2215,7 @@ class BaseMindooTenantDirectory {
|
|
|
1707
2215
|
async writePolicyDoc(docId, policy, administrationPrivateKey, administrationPrivateKeyPassword) {
|
|
1708
2216
|
const baseTenant = this.tenant;
|
|
1709
2217
|
const directoryDB = await this.getDirectoryDB();
|
|
1710
|
-
const adminSigningKeyPair =
|
|
1711
|
-
publicKey: baseTenant.getAdministrationPublicKey(),
|
|
1712
|
-
privateKey: administrationPrivateKey,
|
|
1713
|
-
};
|
|
2218
|
+
const adminSigningKeyPair = await this.adminKeyPair(administrationPrivateKey, administrationPrivateKeyPassword);
|
|
1714
2219
|
const doc = await this.getOrCreateAclDoc(directoryDB, docId, adminSigningKeyPair, administrationPrivateKeyPassword);
|
|
1715
2220
|
await directoryDB.changeDoc(doc, async (d) => {
|
|
1716
2221
|
const data = d.getData();
|
|
@@ -1756,10 +2261,7 @@ class BaseMindooTenantDirectory {
|
|
|
1756
2261
|
(0, types_3.validateAclRule)(ruleDoc);
|
|
1757
2262
|
const baseTenant = this.tenant;
|
|
1758
2263
|
const directoryDB = await this.getDirectoryDB();
|
|
1759
|
-
const adminSigningKeyPair =
|
|
1760
|
-
publicKey: baseTenant.getAdministrationPublicKey(),
|
|
1761
|
-
privateKey: administrationPrivateKey,
|
|
1762
|
-
};
|
|
2264
|
+
const adminSigningKeyPair = await this.adminKeyPair(administrationPrivateKey, administrationPrivateKeyPassword);
|
|
1763
2265
|
const doc = await this.getOrCreateAclDoc(directoryDB, (0, types_3.aclRuleDocId)(rule.ruleId), adminSigningKeyPair, administrationPrivateKeyPassword);
|
|
1764
2266
|
await directoryDB.changeDoc(doc, async (d) => {
|
|
1765
2267
|
const data = d.getData();
|
|
@@ -1811,10 +2313,7 @@ class BaseMindooTenantDirectory {
|
|
|
1811
2313
|
async deleteRule(ruleId, administrationPrivateKey, administrationPrivateKeyPassword) {
|
|
1812
2314
|
const baseTenant = this.tenant;
|
|
1813
2315
|
const directoryDB = await this.getDirectoryDB();
|
|
1814
|
-
const adminSigningKeyPair =
|
|
1815
|
-
publicKey: baseTenant.getAdministrationPublicKey(),
|
|
1816
|
-
privateKey: administrationPrivateKey,
|
|
1817
|
-
};
|
|
2316
|
+
const adminSigningKeyPair = await this.adminKeyPair(administrationPrivateKey, administrationPrivateKeyPassword);
|
|
1818
2317
|
try {
|
|
1819
2318
|
await directoryDB.deleteDocument((0, types_3.aclRuleDocId)(ruleId), {
|
|
1820
2319
|
signingKeyPair: adminSigningKeyPair,
|
|
@@ -1833,10 +2332,9 @@ class BaseMindooTenantDirectory {
|
|
|
1833
2332
|
async addTrustedWitness(witness, administrationPrivateKey, administrationPrivateKeyPassword) {
|
|
1834
2333
|
const baseTenant = this.tenant;
|
|
1835
2334
|
const directoryDB = await this.getDirectoryDB();
|
|
1836
|
-
const adminSigningKeyPair =
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
};
|
|
2335
|
+
const adminSigningKeyPair = await this.adminKeyPair(administrationPrivateKey, administrationPrivateKeyPassword);
|
|
2336
|
+
// Trusted witnesses decide whose receipt times count (§6.13): root only.
|
|
2337
|
+
await this.rootKeyPair(administrationPrivateKey, administrationPrivateKeyPassword, "add trusted witnesses");
|
|
1840
2338
|
const fingerprint = await this.sha256Hex(witness.witnessPublicKey);
|
|
1841
2339
|
const doc = await this.getOrCreateAclDoc(directoryDB, (0, types_2.aclTrustedWitnessDocId)(fingerprint), adminSigningKeyPair, administrationPrivateKeyPassword);
|
|
1842
2340
|
await directoryDB.changeDoc(doc, async (d) => {
|
|
@@ -1853,10 +2351,8 @@ class BaseMindooTenantDirectory {
|
|
|
1853
2351
|
async removeTrustedWitness(witnessPublicKey, administrationPrivateKey, administrationPrivateKeyPassword) {
|
|
1854
2352
|
const baseTenant = this.tenant;
|
|
1855
2353
|
const directoryDB = await this.getDirectoryDB();
|
|
1856
|
-
const adminSigningKeyPair =
|
|
1857
|
-
|
|
1858
|
-
privateKey: administrationPrivateKey,
|
|
1859
|
-
};
|
|
2354
|
+
const adminSigningKeyPair = await this.adminKeyPair(administrationPrivateKey, administrationPrivateKeyPassword);
|
|
2355
|
+
await this.rootKeyPair(administrationPrivateKey, administrationPrivateKeyPassword, "remove trusted witnesses");
|
|
1860
2356
|
const fingerprint = await this.sha256Hex(witnessPublicKey);
|
|
1861
2357
|
try {
|
|
1862
2358
|
await directoryDB.deleteDocument((0, types_2.aclTrustedWitnessDocId)(fingerprint), {
|
|
@@ -2076,6 +2572,15 @@ class BaseMindooTenantDirectory {
|
|
|
2076
2572
|
? await this.encryptUserDetailsForTenant(this.buildUserDetailsPayload(username, changes.details))
|
|
2077
2573
|
: null;
|
|
2078
2574
|
const identityHashes = await this.computeIdentityHashes(username);
|
|
2575
|
+
// Devices this person certified (userkeys.md §9.6) can be revoked and
|
|
2576
|
+
// wiped here like listed ones, although they are not on the grant.
|
|
2577
|
+
const certifiedByHash = new Map();
|
|
2578
|
+
for (const grant of await this.findGrantAccessDocuments(username)) {
|
|
2579
|
+
const hash = grant.getData().username_hash;
|
|
2580
|
+
if (typeof hash === "string" && !certifiedByHash.has(hash)) {
|
|
2581
|
+
certifiedByHash.set(hash, await this.certificatesOfPerson(hash));
|
|
2582
|
+
}
|
|
2583
|
+
}
|
|
2079
2584
|
await this.editGrantArrays(username, administrationPrivateKey, administrationPrivateKeyPassword, (data) => {
|
|
2080
2585
|
if (userDetailsEncrypted !== null) {
|
|
2081
2586
|
data.user_details_encrypted = userDetailsEncrypted;
|
|
@@ -2107,12 +2612,52 @@ class BaseMindooTenantDirectory {
|
|
|
2107
2612
|
}
|
|
2108
2613
|
return p;
|
|
2109
2614
|
});
|
|
2110
|
-
|
|
2615
|
+
const certified = typeof data.username_hash === "string" ? certifiedByHash.get(data.username_hash) ?? [] : [];
|
|
2616
|
+
const isCertifiedOnly = (key) => certified.some((c) => (0, DirectoryStateNode_1.signingKeysEqual)(c.deviceSigningPublicKey, key)) &&
|
|
2617
|
+
!(0, grantKeys_1.extractActiveKeyPairs)(data).some((pair) => (0, DirectoryStateNode_1.signingKeysEqual)(pair.signingPublicKey, key));
|
|
2618
|
+
for (const cert of certified) {
|
|
2619
|
+
const key = cert.deviceSigningPublicKey;
|
|
2620
|
+
if (!revokeSet.has(key) || next.some((p) => (0, DirectoryStateNode_1.signingKeysEqual)(p.signingPublicKey, key)))
|
|
2621
|
+
continue;
|
|
2622
|
+
next.push({
|
|
2623
|
+
signingPublicKey: key,
|
|
2624
|
+
encryptionPublicKey: cert.deviceEncryptionPublicKey,
|
|
2625
|
+
...(cert.label ? { label: cert.label } : {}),
|
|
2626
|
+
revoked: true,
|
|
2627
|
+
revokedAt: now,
|
|
2628
|
+
});
|
|
2629
|
+
}
|
|
2630
|
+
// Restoring a certified device only lifts the revocation; it does not
|
|
2631
|
+
// turn it into a device listed on the grant.
|
|
2632
|
+
const kept = next.filter((p) => !(restoreSet.has(p.signingPublicKey) && isCertifiedOnly(p.signingPublicKey) && !p.revoked));
|
|
2633
|
+
(0, grantKeys_1.applyKeyPairFields)(data, kept);
|
|
2111
2634
|
// Remote-wipe set: authoritative when supplied (else preserve existing),
|
|
2112
|
-
// restricted to keys present on this grant doc
|
|
2113
|
-
|
|
2635
|
+
// restricted to keys present on this grant doc or certified by its
|
|
2636
|
+
// person, and never for restored keys.
|
|
2637
|
+
const present = new Set([
|
|
2638
|
+
...kept.map((p) => p.signingPublicKey),
|
|
2639
|
+
...certified.map((c) => c.deviceSigningPublicKey),
|
|
2640
|
+
]);
|
|
2114
2641
|
const desiredWipe = changes.wipeSigningKeys ?? (0, grantKeys_1.extractWipeRequestedSigningKeys)(data);
|
|
2115
2642
|
data.wipeRequestedForSigningKeys = Array.from(new Set(desiredWipe.filter((k) => present.has(k) && !restoreSet.has(k))));
|
|
2643
|
+
if (changes.deviceSelfGrant !== undefined) {
|
|
2644
|
+
const existing = (0, grantKeys_1.extractDeviceSelfGrant)(data);
|
|
2645
|
+
if (changes.deviceSelfGrant === null) {
|
|
2646
|
+
if (existing && existing.revokedAt === undefined) {
|
|
2647
|
+
data.deviceSelfGrant = { ...existing, revokedAt: now };
|
|
2648
|
+
}
|
|
2649
|
+
}
|
|
2650
|
+
else {
|
|
2651
|
+
const key = changes.deviceSelfGrant.trim();
|
|
2652
|
+
if (!key)
|
|
2653
|
+
throw new Error("deviceSelfGrant must be a PEM public key or null");
|
|
2654
|
+
if (!existing ||
|
|
2655
|
+
existing.revokedAt !== undefined ||
|
|
2656
|
+
existing.signingPublicKey.replace(/\s+/g, "") !== key.replace(/\s+/g, "")) {
|
|
2657
|
+
data.deviceSelfGrant = { signingPublicKey: key, grantedAt: now };
|
|
2658
|
+
}
|
|
2659
|
+
}
|
|
2660
|
+
}
|
|
2116
2661
|
});
|
|
2117
2662
|
}
|
|
2118
2663
|
/**
|
|
@@ -2398,10 +2943,7 @@ class BaseMindooTenantDirectory {
|
|
|
2398
2943
|
const docId = (0, types_3.aclKeyDistributionDocId)(keyId);
|
|
2399
2944
|
const baseTenant = this.tenant;
|
|
2400
2945
|
const directoryDB = await this.getDirectoryDB();
|
|
2401
|
-
const adminSigningKeyPair =
|
|
2402
|
-
publicKey: baseTenant.getAdministrationPublicKey(),
|
|
2403
|
-
privateKey: administrationPrivateKey,
|
|
2404
|
-
};
|
|
2946
|
+
const adminSigningKeyPair = await this.adminKeyPair(administrationPrivateKey, administrationPrivateKeyPassword);
|
|
2405
2947
|
const doc = await this.getOrCreateAclDoc(directoryDB, docId, adminSigningKeyPair, administrationPrivateKeyPassword);
|
|
2406
2948
|
await directoryDB.changeDoc(doc, async (d) => {
|
|
2407
2949
|
const data = d.getData();
|
|
@@ -2518,10 +3060,7 @@ class BaseMindooTenantDirectory {
|
|
|
2518
3060
|
async deleteKeyDistribution(keyId, administrationPrivateKey, administrationPrivateKeyPassword) {
|
|
2519
3061
|
const baseTenant = this.tenant;
|
|
2520
3062
|
const directoryDB = await this.getDirectoryDB();
|
|
2521
|
-
const adminSigningKeyPair =
|
|
2522
|
-
publicKey: baseTenant.getAdministrationPublicKey(),
|
|
2523
|
-
privateKey: administrationPrivateKey,
|
|
2524
|
-
};
|
|
3063
|
+
const adminSigningKeyPair = await this.adminKeyPair(administrationPrivateKey, administrationPrivateKeyPassword);
|
|
2525
3064
|
try {
|
|
2526
3065
|
await directoryDB.deleteDocument((0, types_3.aclKeyDistributionDocId)(keyId), {
|
|
2527
3066
|
signingKeyPair: adminSigningKeyPair,
|
|
@@ -2778,10 +3317,7 @@ class BaseMindooTenantDirectory {
|
|
|
2778
3317
|
const docId = (0, types_3.aclAppDistributionDocId)(appId);
|
|
2779
3318
|
const baseTenant = this.tenant;
|
|
2780
3319
|
const directoryDB = await this.getDirectoryDB();
|
|
2781
|
-
const adminSigningKeyPair =
|
|
2782
|
-
publicKey: baseTenant.getAdministrationPublicKey(),
|
|
2783
|
-
privateKey: administrationPrivateKey,
|
|
2784
|
-
};
|
|
3320
|
+
const adminSigningKeyPair = await this.adminKeyPair(administrationPrivateKey, administrationPrivateKeyPassword);
|
|
2785
3321
|
const doc = await this.getOrCreateAclDoc(directoryDB, docId, adminSigningKeyPair, administrationPrivateKeyPassword);
|
|
2786
3322
|
await directoryDB.changeDoc(doc, async (d) => {
|
|
2787
3323
|
const data = d.getData();
|
|
@@ -2890,10 +3426,7 @@ class BaseMindooTenantDirectory {
|
|
|
2890
3426
|
async deleteAppDistribution(appId, administrationPrivateKey, administrationPrivateKeyPassword) {
|
|
2891
3427
|
const baseTenant = this.tenant;
|
|
2892
3428
|
const directoryDB = await this.getDirectoryDB();
|
|
2893
|
-
const adminSigningKeyPair =
|
|
2894
|
-
publicKey: baseTenant.getAdministrationPublicKey(),
|
|
2895
|
-
privateKey: administrationPrivateKey,
|
|
2896
|
-
};
|
|
3429
|
+
const adminSigningKeyPair = await this.adminKeyPair(administrationPrivateKey, administrationPrivateKeyPassword);
|
|
2897
3430
|
try {
|
|
2898
3431
|
await directoryDB.deleteDocument((0, types_3.aclAppDistributionDocId)(appId), {
|
|
2899
3432
|
signingKeyPair: adminSigningKeyPair,
|
|
@@ -3017,10 +3550,7 @@ class BaseMindooTenantDirectory {
|
|
|
3017
3550
|
const docId = (0, types_3.aclSyncSetupPolicyDocId)(policyId);
|
|
3018
3551
|
const baseTenant = this.tenant;
|
|
3019
3552
|
const directoryDB = await this.getDirectoryDB();
|
|
3020
|
-
const adminSigningKeyPair =
|
|
3021
|
-
publicKey: baseTenant.getAdministrationPublicKey(),
|
|
3022
|
-
privateKey: administrationPrivateKey,
|
|
3023
|
-
};
|
|
3553
|
+
const adminSigningKeyPair = await this.adminKeyPair(administrationPrivateKey, administrationPrivateKeyPassword);
|
|
3024
3554
|
const doc = await this.getOrCreateAclDoc(directoryDB, docId, adminSigningKeyPair, administrationPrivateKeyPassword);
|
|
3025
3555
|
await directoryDB.changeDoc(doc, async (d) => {
|
|
3026
3556
|
const data = d.getData();
|
|
@@ -3130,10 +3660,7 @@ class BaseMindooTenantDirectory {
|
|
|
3130
3660
|
async deleteSyncSetupPolicy(policyId, administrationPrivateKey, administrationPrivateKeyPassword) {
|
|
3131
3661
|
const baseTenant = this.tenant;
|
|
3132
3662
|
const directoryDB = await this.getDirectoryDB();
|
|
3133
|
-
const adminSigningKeyPair =
|
|
3134
|
-
publicKey: baseTenant.getAdministrationPublicKey(),
|
|
3135
|
-
privateKey: administrationPrivateKey,
|
|
3136
|
-
};
|
|
3663
|
+
const adminSigningKeyPair = await this.adminKeyPair(administrationPrivateKey, administrationPrivateKeyPassword);
|
|
3137
3664
|
try {
|
|
3138
3665
|
await directoryDB.deleteDocument((0, types_3.aclSyncSetupPolicyDocId)(policyId), {
|
|
3139
3666
|
signingKeyPair: adminSigningKeyPair,
|
|
@@ -3212,7 +3739,7 @@ class BaseMindooTenantDirectory {
|
|
|
3212
3739
|
* server-readable and carries every variant.
|
|
3213
3740
|
*/
|
|
3214
3741
|
async getRevokedDecryptionKeyIdsForSigningKey(signingKey) {
|
|
3215
|
-
if (
|
|
3742
|
+
if (await this.isAdminKeyNow(signingKey))
|
|
3216
3743
|
return [];
|
|
3217
3744
|
const lookup = await this.getUserBySigningPublicKey(signingKey);
|
|
3218
3745
|
if (!lookup)
|
|
@@ -3290,10 +3817,7 @@ class BaseMindooTenantDirectory {
|
|
|
3290
3817
|
}
|
|
3291
3818
|
const baseTenant = this.tenant;
|
|
3292
3819
|
const directoryDB = await this.getDirectoryDB();
|
|
3293
|
-
const adminSigningKeyPair =
|
|
3294
|
-
publicKey: baseTenant.getAdministrationPublicKey(),
|
|
3295
|
-
privateKey: administrationPrivateKey,
|
|
3296
|
-
};
|
|
3820
|
+
const adminSigningKeyPair = await this.adminKeyPair(administrationPrivateKey, administrationPrivateKeyPassword);
|
|
3297
3821
|
for (const grant of grants) {
|
|
3298
3822
|
const doc = await directoryDB.getDocument(grant.getId());
|
|
3299
3823
|
await directoryDB.changeDoc(doc, async (d) => {
|
|
@@ -3325,9 +3849,9 @@ class BaseMindooTenantDirectory {
|
|
|
3325
3849
|
*/
|
|
3326
3850
|
async identitySetForSigningKey(signingKey, node, opts) {
|
|
3327
3851
|
// 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
|
|
3852
|
+
// grant document (it authors the directory itself). Delegated admin keys
|
|
3853
|
+
// (§6.13) count while their window covers the evaluation time.
|
|
3854
|
+
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
3855
|
// Audit #4 (revocation lag): resolve the acting identity strictly from the
|
|
3332
3856
|
// directory state at the entry's trusted time `T`. A signing key that was
|
|
3333
3857
|
// not an ACTIVE grant at `T` carries only the pseudo-tokens
|
|
@@ -3337,7 +3861,8 @@ class BaseMindooTenantDirectory {
|
|
|
3337
3861
|
// allow-rules until the trust cache refreshed (up to
|
|
3338
3862
|
// DIRECTORY_SYNC_INTERVAL_MS later). The `node` is the time-`T` snapshot
|
|
3339
3863
|
// whose `bySigningKey` only contains keys whose grant is active at `T`.
|
|
3340
|
-
|
|
3864
|
+
// A certified device (userkeys.md §9.6) acts for its person like a listed one.
|
|
3865
|
+
const grantedAtT = !!(await this.resolveSignerInNode(node, signingKey, opts.trustedTime ?? node.trustedTimeUpperBound));
|
|
3341
3866
|
// Resolve the author's username from their signing key. Trusted non-user
|
|
3342
3867
|
// keys (e.g. server-to-server sync identities) have no grant; treat them as
|
|
3343
3868
|
// an empty identity that still carries `$everyone`/`$author`. Keys that were
|
|
@@ -3560,6 +4085,30 @@ class BaseMindooTenantDirectory {
|
|
|
3560
4085
|
if (key.replace(/\s+/g, "") === needle)
|
|
3561
4086
|
return lookup;
|
|
3562
4087
|
}
|
|
4088
|
+
// A device the person certified (userkeys.md §9.6). Like the cache above,
|
|
4089
|
+
// this keeps naming the person after the device was revoked; callers that
|
|
4090
|
+
// need "active" check the signing-key universe or resolveSignerToGrant.
|
|
4091
|
+
const certified = await this.certifiedGrantInNode(await this.getDirectoryStateHead(), publicKey, Number.MAX_SAFE_INTEGER, { enforceRevocation: false });
|
|
4092
|
+
if (!certified)
|
|
4093
|
+
return null;
|
|
4094
|
+
const personKeys = [
|
|
4095
|
+
...certified.grant.signingKeys,
|
|
4096
|
+
...(certified.grant.revokedSigningKeys ?? []),
|
|
4097
|
+
];
|
|
4098
|
+
for (const personKey of personKeys) {
|
|
4099
|
+
const lookup = this.userLookupCache.get(personKey) ??
|
|
4100
|
+
[...this.userLookupCache.entries()].find(([k]) => (0, DirectoryStateNode_1.signingKeysEqual)(k, personKey))?.[1];
|
|
4101
|
+
if (lookup) {
|
|
4102
|
+
// Device-specific fields come from the certificate; a join response
|
|
4103
|
+
// wrapped for another device of the person must not be handed out.
|
|
4104
|
+
const { joinResponseWrapped: _other, ...personLookup } = lookup;
|
|
4105
|
+
return {
|
|
4106
|
+
...personLookup,
|
|
4107
|
+
signingPublicKey: certified.cert.deviceSigningPublicKey,
|
|
4108
|
+
encryptionPublicKey: certified.cert.deviceEncryptionPublicKey,
|
|
4109
|
+
};
|
|
4110
|
+
}
|
|
4111
|
+
}
|
|
3563
4112
|
return null;
|
|
3564
4113
|
}
|
|
3565
4114
|
/**
|
|
@@ -3577,12 +4126,20 @@ class BaseMindooTenantDirectory {
|
|
|
3577
4126
|
const grants = await this.findGrantAccessDocuments(username);
|
|
3578
4127
|
const active = new Set();
|
|
3579
4128
|
const wipeRequested = new Set();
|
|
4129
|
+
const hashes = new Set();
|
|
3580
4130
|
for (const grant of grants) {
|
|
3581
4131
|
const data = grant.getData();
|
|
3582
4132
|
for (const key of (0, grantKeys_1.extractSigningPublicKeys)(data))
|
|
3583
4133
|
active.add(key);
|
|
3584
4134
|
for (const key of (0, grantKeys_1.extractWipeRequestedSigningKeys)(data))
|
|
3585
4135
|
wipeRequested.add(key);
|
|
4136
|
+
if (typeof data.username_hash === "string")
|
|
4137
|
+
hashes.add(data.username_hash);
|
|
4138
|
+
}
|
|
4139
|
+
// Devices the person certified count as active (userkeys.md §9.6).
|
|
4140
|
+
for (const hash of hashes) {
|
|
4141
|
+
for (const cert of await this.listCertifiedDevices(hash))
|
|
4142
|
+
active.add(cert.deviceSigningPublicKey);
|
|
3586
4143
|
}
|
|
3587
4144
|
return { active: Array.from(active), wipeRequested: Array.from(wipeRequested) };
|
|
3588
4145
|
}
|
|
@@ -3666,10 +4223,7 @@ class BaseMindooTenantDirectory {
|
|
|
3666
4223
|
const requestedAt = (0, timeSource_1.semanticNow)();
|
|
3667
4224
|
const baseTenant = this.tenant;
|
|
3668
4225
|
const directoryDB = await this.getDirectoryDB();
|
|
3669
|
-
const adminSigningKeyPair =
|
|
3670
|
-
publicKey: baseTenant.getAdministrationPublicKey(),
|
|
3671
|
-
privateKey: administrationPrivateKey,
|
|
3672
|
-
};
|
|
4226
|
+
const adminSigningKeyPair = await this.adminKeyPair(administrationPrivateKey, administrationPrivateKeyPassword);
|
|
3673
4227
|
const doc = await this.getOrCreateAclDoc(directoryDB, docId, adminSigningKeyPair, administrationPrivateKeyPassword);
|
|
3674
4228
|
await directoryDB.changeDoc(doc, async (d) => {
|
|
3675
4229
|
const data = d.getData();
|
|
@@ -3748,10 +4302,7 @@ class BaseMindooTenantDirectory {
|
|
|
3748
4302
|
async deleteDocHistoryPurge(requestId, administrationPrivateKey, administrationPrivateKeyPassword) {
|
|
3749
4303
|
const baseTenant = this.tenant;
|
|
3750
4304
|
const directoryDB = await this.getDirectoryDB();
|
|
3751
|
-
const adminSigningKeyPair =
|
|
3752
|
-
publicKey: baseTenant.getAdministrationPublicKey(),
|
|
3753
|
-
privateKey: administrationPrivateKey,
|
|
3754
|
-
};
|
|
4305
|
+
const adminSigningKeyPair = await this.adminKeyPair(administrationPrivateKey, administrationPrivateKeyPassword);
|
|
3755
4306
|
try {
|
|
3756
4307
|
await directoryDB.deleteDocument((0, types_3.aclDocHistoryPurgeDocId)(requestId), {
|
|
3757
4308
|
signingKeyPair: adminSigningKeyPair,
|
|
@@ -3800,10 +4351,7 @@ class BaseMindooTenantDirectory {
|
|
|
3800
4351
|
async changeTenantSettings(changeFunc, administrationPrivateKey, administrationPrivateKeyPassword) {
|
|
3801
4352
|
const baseTenant = this.tenant;
|
|
3802
4353
|
const directoryDB = await this.getDirectoryDB();
|
|
3803
|
-
const adminSigningKeyPair =
|
|
3804
|
-
publicKey: baseTenant.getAdministrationPublicKey(),
|
|
3805
|
-
privateKey: administrationPrivateKey,
|
|
3806
|
-
};
|
|
4354
|
+
const adminSigningKeyPair = await this.adminKeyPair(administrationPrivateKey, administrationPrivateKeyPassword);
|
|
3807
4355
|
// Get cached settings doc or create new one
|
|
3808
4356
|
let settingsDoc = await this.getTenantSettings();
|
|
3809
4357
|
if (!settingsDoc) {
|
|
@@ -3827,10 +4375,7 @@ class BaseMindooTenantDirectory {
|
|
|
3827
4375
|
async changeDBSettings(dbId, changeFunc, administrationPrivateKey, administrationPrivateKeyPassword) {
|
|
3828
4376
|
const baseTenant = this.tenant;
|
|
3829
4377
|
const directoryDB = await this.getDirectoryDB();
|
|
3830
|
-
const adminSigningKeyPair =
|
|
3831
|
-
publicKey: baseTenant.getAdministrationPublicKey(),
|
|
3832
|
-
privateKey: administrationPrivateKey,
|
|
3833
|
-
};
|
|
4378
|
+
const adminSigningKeyPair = await this.adminKeyPair(administrationPrivateKey, administrationPrivateKeyPassword);
|
|
3834
4379
|
// Get cached settings doc for this dbId or create new one
|
|
3835
4380
|
let settingsDoc = await this.getDBSettings(dbId);
|
|
3836
4381
|
if (!settingsDoc) {
|
|
@@ -3885,10 +4430,7 @@ class BaseMindooTenantDirectory {
|
|
|
3885
4430
|
const normalizedGroupName = this.normalizeGroupName(groupName);
|
|
3886
4431
|
const baseTenant = this.tenant;
|
|
3887
4432
|
const directoryDB = await this.getDirectoryDB();
|
|
3888
|
-
const adminSigningKeyPair =
|
|
3889
|
-
publicKey: baseTenant.getAdministrationPublicKey(),
|
|
3890
|
-
privateKey: administrationPrivateKey,
|
|
3891
|
-
};
|
|
4433
|
+
const adminSigningKeyPair = await this.adminKeyPair(administrationPrivateKey, administrationPrivateKeyPassword);
|
|
3892
4434
|
// Compute hashes (salted v2 for new writes) and encrypted values for all
|
|
3893
4435
|
// new usernames. Reads match both v1 and v2 forms (docs/accesscontrol.md §6.5).
|
|
3894
4436
|
const newMembersHashes = await Promise.all(username.map(u => this.hashUsernameForWrite(u)));
|
|
@@ -3958,10 +4500,7 @@ class BaseMindooTenantDirectory {
|
|
|
3958
4500
|
// Group doesn't exist, nothing to do
|
|
3959
4501
|
return;
|
|
3960
4502
|
}
|
|
3961
|
-
const adminSigningKeyPair =
|
|
3962
|
-
publicKey: baseTenant.getAdministrationPublicKey(),
|
|
3963
|
-
privateKey: administrationPrivateKey,
|
|
3964
|
-
};
|
|
4503
|
+
const adminSigningKeyPair = await this.adminKeyPair(administrationPrivateKey, administrationPrivateKeyPassword);
|
|
3965
4504
|
// Compute hashes of usernames to remove. Include both legacy (v1) and
|
|
3966
4505
|
// salted (v2) forms so members stored under either scheme are removed.
|
|
3967
4506
|
const hashesToRemove = new Set((await Promise.all(username.map(u => this.usernameHashCandidates(u)))).flat());
|
|
@@ -4020,10 +4559,7 @@ class BaseMindooTenantDirectory {
|
|
|
4020
4559
|
// Group doesn't exist, nothing to do
|
|
4021
4560
|
return;
|
|
4022
4561
|
}
|
|
4023
|
-
const adminSigningKeyPair =
|
|
4024
|
-
publicKey: baseTenant.getAdministrationPublicKey(),
|
|
4025
|
-
privateKey: administrationPrivateKey,
|
|
4026
|
-
};
|
|
4562
|
+
const adminSigningKeyPair = await this.adminKeyPair(administrationPrivateKey, administrationPrivateKeyPassword);
|
|
4027
4563
|
// Delete the document using the proper API
|
|
4028
4564
|
await directoryDB.deleteDocument(cachedGroup.docId, {
|
|
4029
4565
|
signingKeyPair: adminSigningKeyPair,
|