mindoodb 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +263 -0
- package/dist/appendonlystores/InMemoryContentAddressedStoreFactory.d.ts +5 -0
- package/dist/appendonlystores/InMemoryContentAddressedStoreFactory.d.ts.map +1 -0
- package/dist/appendonlystores/InMemoryContentAddressedStoreFactory.js +17 -0
- package/dist/appendonlystores/InMemoryContentAddressedStoreFactory.js.map +1 -0
- package/dist/appendonlystores/network/ClientNetworkContentAddressedStore.d.ts +111 -0
- package/dist/appendonlystores/network/ClientNetworkContentAddressedStore.d.ts.map +1 -0
- package/dist/appendonlystores/network/ClientNetworkContentAddressedStore.js +237 -0
- package/dist/appendonlystores/network/ClientNetworkContentAddressedStore.js.map +1 -0
- package/dist/appendonlystores/network/HttpTransport.d.ts +78 -0
- package/dist/appendonlystores/network/HttpTransport.d.ts.map +1 -0
- package/dist/appendonlystores/network/HttpTransport.js +356 -0
- package/dist/appendonlystores/network/HttpTransport.js.map +1 -0
- package/dist/appendonlystores/network/ServerNetworkContentAddressedStore.d.ts +131 -0
- package/dist/appendonlystores/network/ServerNetworkContentAddressedStore.d.ts.map +1 -0
- package/dist/appendonlystores/network/ServerNetworkContentAddressedStore.js +248 -0
- package/dist/appendonlystores/network/ServerNetworkContentAddressedStore.js.map +1 -0
- package/dist/appendonlystores/network/index.d.ts +8 -0
- package/dist/appendonlystores/network/index.d.ts.map +1 -0
- package/dist/appendonlystores/network/index.js +21 -0
- package/dist/appendonlystores/network/index.js.map +1 -0
- package/dist/browser/crypto/BrowserCryptoAdapter.d.ts +14 -0
- package/dist/browser/crypto/BrowserCryptoAdapter.d.ts.map +1 -0
- package/dist/browser/crypto/BrowserCryptoAdapter.js +30 -0
- package/dist/browser/crypto/BrowserCryptoAdapter.js.map +1 -0
- package/dist/browser/index.d.ts +3 -0
- package/dist/browser/index.d.ts.map +1 -0
- package/dist/browser/index.js +26 -0
- package/dist/browser/index.js.map +1 -0
- package/dist/core/BaseMindooDB.d.ts +182 -0
- package/dist/core/BaseMindooDB.d.ts.map +1 -0
- package/dist/core/BaseMindooDB.js +1701 -0
- package/dist/core/BaseMindooDB.js.map +1 -0
- package/dist/core/BaseMindooTenant.d.ts +155 -0
- package/dist/core/BaseMindooTenant.d.ts.map +1 -0
- package/dist/core/BaseMindooTenant.js +673 -0
- package/dist/core/BaseMindooTenant.js.map +1 -0
- package/dist/core/BaseMindooTenantDirectory.d.ts +99 -0
- package/dist/core/BaseMindooTenantDirectory.d.ts.map +1 -0
- package/dist/core/BaseMindooTenantDirectory.js +776 -0
- package/dist/core/BaseMindooTenantDirectory.js.map +1 -0
- package/dist/core/BaseMindooTenantFactory.d.ts +91 -0
- package/dist/core/BaseMindooTenantFactory.d.ts.map +1 -0
- package/dist/core/BaseMindooTenantFactory.js +297 -0
- package/dist/core/BaseMindooTenantFactory.js.map +1 -0
- package/dist/core/appendonlystores/InMemoryContentAddressedStore.d.ts +111 -0
- package/dist/core/appendonlystores/InMemoryContentAddressedStore.d.ts.map +1 -0
- package/dist/core/appendonlystores/InMemoryContentAddressedStore.js +293 -0
- package/dist/core/appendonlystores/InMemoryContentAddressedStore.js.map +1 -0
- package/dist/core/appendonlystores/network/AuthenticationService.d.ts +78 -0
- package/dist/core/appendonlystores/network/AuthenticationService.d.ts.map +1 -0
- package/dist/core/appendonlystores/network/AuthenticationService.js +297 -0
- package/dist/core/appendonlystores/network/AuthenticationService.js.map +1 -0
- package/dist/core/appendonlystores/network/NetworkTransport.d.ts +174 -0
- package/dist/core/appendonlystores/network/NetworkTransport.d.ts.map +1 -0
- package/dist/core/appendonlystores/network/NetworkTransport.js +3 -0
- package/dist/core/appendonlystores/network/NetworkTransport.js.map +1 -0
- package/dist/core/appendonlystores/network/types.d.ts +135 -0
- package/dist/core/appendonlystores/network/types.d.ts.map +1 -0
- package/dist/core/appendonlystores/network/types.js +35 -0
- package/dist/core/appendonlystores/network/types.js.map +1 -0
- package/dist/core/appendonlystores/types.d.ts +157 -0
- package/dist/core/appendonlystores/types.d.ts.map +1 -0
- package/dist/core/appendonlystores/types.js +3 -0
- package/dist/core/appendonlystores/types.js.map +1 -0
- package/dist/core/crypto/CryptoAdapter.d.ts +19 -0
- package/dist/core/crypto/CryptoAdapter.d.ts.map +1 -0
- package/dist/core/crypto/CryptoAdapter.js +3 -0
- package/dist/core/crypto/CryptoAdapter.js.map +1 -0
- package/dist/core/crypto/MindooDocSigner.d.ts +106 -0
- package/dist/core/crypto/MindooDocSigner.d.ts.map +1 -0
- package/dist/core/crypto/MindooDocSigner.js +192 -0
- package/dist/core/crypto/MindooDocSigner.js.map +1 -0
- package/dist/core/crypto/RSAEncryption.d.ts +62 -0
- package/dist/core/crypto/RSAEncryption.d.ts.map +1 -0
- package/dist/core/crypto/RSAEncryption.js +167 -0
- package/dist/core/crypto/RSAEncryption.js.map +1 -0
- package/dist/core/errors.d.ts +10 -0
- package/dist/core/errors.d.ts.map +1 -0
- package/dist/core/errors.js +17 -0
- package/dist/core/errors.js.map +1 -0
- package/dist/core/index.d.ts +14 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +53 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/indexing/virtualviews/IViewEntryAccessCheck.d.ts +33 -0
- package/dist/core/indexing/virtualviews/IViewEntryAccessCheck.d.ts.map +1 -0
- package/dist/core/indexing/virtualviews/IViewEntryAccessCheck.js +27 -0
- package/dist/core/indexing/virtualviews/IViewEntryAccessCheck.js.map +1 -0
- package/dist/core/indexing/virtualviews/IVirtualViewDataProvider.d.ts +31 -0
- package/dist/core/indexing/virtualviews/IVirtualViewDataProvider.d.ts.map +1 -0
- package/dist/core/indexing/virtualviews/IVirtualViewDataProvider.js +3 -0
- package/dist/core/indexing/virtualviews/IVirtualViewDataProvider.js.map +1 -0
- package/dist/core/indexing/virtualviews/MindooDBVirtualViewDataProvider.d.ts +52 -0
- package/dist/core/indexing/virtualviews/MindooDBVirtualViewDataProvider.d.ts.map +1 -0
- package/dist/core/indexing/virtualviews/MindooDBVirtualViewDataProvider.js +106 -0
- package/dist/core/indexing/virtualviews/MindooDBVirtualViewDataProvider.js.map +1 -0
- package/dist/core/indexing/virtualviews/ViewEntrySortKey.d.ts +44 -0
- package/dist/core/indexing/virtualviews/ViewEntrySortKey.d.ts.map +1 -0
- package/dist/core/indexing/virtualviews/ViewEntrySortKey.js +107 -0
- package/dist/core/indexing/virtualviews/ViewEntrySortKey.js.map +1 -0
- package/dist/core/indexing/virtualviews/ViewEntrySortKeyComparator.d.ts +28 -0
- package/dist/core/indexing/virtualviews/ViewEntrySortKeyComparator.d.ts.map +1 -0
- package/dist/core/indexing/virtualviews/ViewEntrySortKeyComparator.js +133 -0
- package/dist/core/indexing/virtualviews/ViewEntrySortKeyComparator.js.map +1 -0
- package/dist/core/indexing/virtualviews/VirtualView.d.ts +109 -0
- package/dist/core/indexing/virtualviews/VirtualView.d.ts.map +1 -0
- package/dist/core/indexing/virtualviews/VirtualView.js +504 -0
- package/dist/core/indexing/virtualviews/VirtualView.js.map +1 -0
- package/dist/core/indexing/virtualviews/VirtualViewColumn.d.ts +59 -0
- package/dist/core/indexing/virtualviews/VirtualViewColumn.d.ts.map +1 -0
- package/dist/core/indexing/virtualviews/VirtualViewColumn.js +70 -0
- package/dist/core/indexing/virtualviews/VirtualViewColumn.js.map +1 -0
- package/dist/core/indexing/virtualviews/VirtualViewDataChange.d.ts +49 -0
- package/dist/core/indexing/virtualviews/VirtualViewDataChange.d.ts.map +1 -0
- package/dist/core/indexing/virtualviews/VirtualViewDataChange.js +69 -0
- package/dist/core/indexing/virtualviews/VirtualViewDataChange.js.map +1 -0
- package/dist/core/indexing/virtualviews/VirtualViewEntryData.d.ts +121 -0
- package/dist/core/indexing/virtualviews/VirtualViewEntryData.d.ts.map +1 -0
- package/dist/core/indexing/virtualviews/VirtualViewEntryData.js +365 -0
- package/dist/core/indexing/virtualviews/VirtualViewEntryData.js.map +1 -0
- package/dist/core/indexing/virtualviews/VirtualViewFactory.d.ts +131 -0
- package/dist/core/indexing/virtualviews/VirtualViewFactory.d.ts.map +1 -0
- package/dist/core/indexing/virtualviews/VirtualViewFactory.js +236 -0
- package/dist/core/indexing/virtualviews/VirtualViewFactory.js.map +1 -0
- package/dist/core/indexing/virtualviews/VirtualViewNavigator.d.ts +330 -0
- package/dist/core/indexing/virtualviews/VirtualViewNavigator.d.ts.map +1 -0
- package/dist/core/indexing/virtualviews/VirtualViewNavigator.js +1010 -0
- package/dist/core/indexing/virtualviews/VirtualViewNavigator.js.map +1 -0
- package/dist/core/indexing/virtualviews/index.d.ts +53 -0
- package/dist/core/indexing/virtualviews/index.d.ts.map +1 -0
- package/dist/core/indexing/virtualviews/index.js +89 -0
- package/dist/core/indexing/virtualviews/index.js.map +1 -0
- package/dist/core/indexing/virtualviews/types.d.ts +115 -0
- package/dist/core/indexing/virtualviews/types.d.ts.map +1 -0
- package/dist/core/indexing/virtualviews/types.js +175 -0
- package/dist/core/indexing/virtualviews/types.js.map +1 -0
- package/dist/core/keys/KeyBag.d.ts +122 -0
- package/dist/core/keys/KeyBag.d.ts.map +1 -0
- package/dist/core/keys/KeyBag.js +401 -0
- package/dist/core/keys/KeyBag.js.map +1 -0
- package/dist/core/types.d.ts +1004 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +10 -0
- package/dist/core/types.js.map +1 -0
- package/dist/core/userid/index.d.ts +53 -0
- package/dist/core/userid/index.d.ts.map +1 -0
- package/dist/core/userid/index.js +3 -0
- package/dist/core/userid/index.js.map +1 -0
- package/dist/core/utils/idGeneration.d.ts +109 -0
- package/dist/core/utils/idGeneration.d.ts.map +1 -0
- package/dist/core/utils/idGeneration.js +182 -0
- package/dist/core/utils/idGeneration.js.map +1 -0
- package/dist/core/utils/index.d.ts +5 -0
- package/dist/core/utils/index.d.ts.map +1 -0
- package/dist/core/utils/index.js +19 -0
- package/dist/core/utils/index.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +40 -0
- package/dist/index.js.map +1 -0
- package/dist/node/crypto/NodeCryptoAdapter.d.ts +16 -0
- package/dist/node/crypto/NodeCryptoAdapter.d.ts.map +1 -0
- package/dist/node/crypto/NodeCryptoAdapter.js +53 -0
- package/dist/node/crypto/NodeCryptoAdapter.js.map +1 -0
- package/dist/node/index.d.ts +3 -0
- package/dist/node/index.d.ts.map +1 -0
- package/dist/node/index.js +26 -0
- package/dist/node/index.js.map +1 -0
- package/package.json +58 -0
|
@@ -0,0 +1,673 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.BaseMindooTenant = void 0;
|
|
4
|
+
const BaseMindooDB_1 = require("./BaseMindooDB");
|
|
5
|
+
const BaseMindooTenantDirectory_1 = require("./BaseMindooTenantDirectory");
|
|
6
|
+
const MindooDocSigner_1 = require("./crypto/MindooDocSigner");
|
|
7
|
+
const errors_1 = require("./errors");
|
|
8
|
+
/**
|
|
9
|
+
* BaseMindooTenant is a platform-agnostic implementation of MindooTenant
|
|
10
|
+
* that works in both browser and server environments.
|
|
11
|
+
*
|
|
12
|
+
* It uses a CryptoAdapter to abstract platform-specific crypto operations,
|
|
13
|
+
* allowing the same implementation to work in browsers and Node.js.
|
|
14
|
+
*
|
|
15
|
+
* Dependencies:
|
|
16
|
+
* - Web Crypto API: Available in both browser (window.crypto) and Node.js (crypto.webcrypto)
|
|
17
|
+
* - AES-256-GCM encryption (widely supported)
|
|
18
|
+
* - Ed25519 signing/verification (Node.js 15+, Chrome 92+)
|
|
19
|
+
* - RSA/ECDH encryption (for encrypting named symmetric keys map)
|
|
20
|
+
* - PBKDF2 key derivation (for password-based key derivation)
|
|
21
|
+
*/
|
|
22
|
+
class BaseMindooTenant {
|
|
23
|
+
constructor(factory, tenantId, tenantEncryptionKey, tenantEncryptionKeyPassword, administrationPublicKey, administrationEncryptionPublicKey, currentUser, currentUserPassword, keyBag, storeFactory, cryptoAdapter) {
|
|
24
|
+
this.databaseCache = new Map();
|
|
25
|
+
this.directoryCache = null;
|
|
26
|
+
this.factory = factory;
|
|
27
|
+
this.tenantId = tenantId;
|
|
28
|
+
this.tenantEncryptionKey = tenantEncryptionKey;
|
|
29
|
+
this.tenantEncryptionKeyPassword = tenantEncryptionKeyPassword;
|
|
30
|
+
this.administrationPublicKey = administrationPublicKey;
|
|
31
|
+
this.administrationEncryptionPublicKey = administrationEncryptionPublicKey;
|
|
32
|
+
this.currentUser = currentUser;
|
|
33
|
+
this.currentUserPassword = currentUserPassword;
|
|
34
|
+
this.keyBag = keyBag;
|
|
35
|
+
this.storeFactory = storeFactory;
|
|
36
|
+
// Import createCryptoAdapter dynamically to avoid issues in browser environments
|
|
37
|
+
if (!cryptoAdapter) {
|
|
38
|
+
const { createCryptoAdapter } = require("./crypto/CryptoAdapter");
|
|
39
|
+
this.cryptoAdapter = createCryptoAdapter();
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
this.cryptoAdapter = cryptoAdapter;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Initialize the tenant.
|
|
47
|
+
*/
|
|
48
|
+
async initialize() {
|
|
49
|
+
console.log(`[BaseMindooTenant] Initializing tenant ${this.tenantId}`);
|
|
50
|
+
// KeyBag is already loaded by the caller before passing it to the constructor
|
|
51
|
+
const keyCount = (await this.keyBag.listKeys()).length;
|
|
52
|
+
console.log(`[BaseMindooTenant] Tenant initialized with ${keyCount} keys in KeyBag`);
|
|
53
|
+
}
|
|
54
|
+
getCryptoAdapter() {
|
|
55
|
+
return this.cryptoAdapter;
|
|
56
|
+
}
|
|
57
|
+
getFactory() {
|
|
58
|
+
return this.factory;
|
|
59
|
+
}
|
|
60
|
+
getId() {
|
|
61
|
+
return this.tenantId;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Get the administration public key for this tenant.
|
|
65
|
+
* Used for creating document signers for administrative operations.
|
|
66
|
+
*
|
|
67
|
+
* @return The administration public key (Ed25519, PEM format)
|
|
68
|
+
*/
|
|
69
|
+
getAdministrationPublicKey() {
|
|
70
|
+
return this.administrationPublicKey;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Get the administration encryption public key for this tenant.
|
|
74
|
+
* Used for encrypting sensitive data in the directory that only admins can decrypt
|
|
75
|
+
* (e.g., usernames in access control documents).
|
|
76
|
+
*
|
|
77
|
+
* @return The administration encryption public key (RSA-OAEP, PEM format)
|
|
78
|
+
*/
|
|
79
|
+
getAdministrationEncryptionPublicKey() {
|
|
80
|
+
return this.administrationEncryptionPublicKey;
|
|
81
|
+
}
|
|
82
|
+
getTenantEncryptionKey() {
|
|
83
|
+
return this.tenantEncryptionKey;
|
|
84
|
+
}
|
|
85
|
+
async encryptPayload(payload, decryptionKeyId) {
|
|
86
|
+
console.log(`[BaseMindooTenant] Encrypting payload with key: ${decryptionKeyId}`);
|
|
87
|
+
console.log(`[BaseMindooTenant] Payload size: ${payload.length} bytes`);
|
|
88
|
+
// Get the symmetric key for this key ID
|
|
89
|
+
let symmetricKey;
|
|
90
|
+
try {
|
|
91
|
+
if (decryptionKeyId === "default") {
|
|
92
|
+
console.log(`[BaseMindooTenant] Using default key (tenant encryption key)`);
|
|
93
|
+
// Use cached tenant encryption key if available
|
|
94
|
+
if (this.decryptedTenantKeyCache) {
|
|
95
|
+
symmetricKey = this.decryptedTenantKeyCache;
|
|
96
|
+
console.log(`[BaseMindooTenant] Using cached tenant key, length: ${symmetricKey.length} bytes`);
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
console.log(`[BaseMindooTenant] Decrypting tenant encryption key`);
|
|
100
|
+
// Decrypt tenant encryption key
|
|
101
|
+
const decrypted = await this.decryptPrivateKey(this.tenantEncryptionKey, this.tenantEncryptionKeyPassword, "default");
|
|
102
|
+
symmetricKey = new Uint8Array(decrypted);
|
|
103
|
+
this.decryptedTenantKeyCache = symmetricKey;
|
|
104
|
+
console.log(`[BaseMindooTenant] Decrypted tenant key, length: ${symmetricKey.length} bytes`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
console.log(`[BaseMindooTenant] Getting named key from KeyBag: ${decryptionKeyId}`);
|
|
109
|
+
// Get the decrypted key from KeyBag
|
|
110
|
+
const decryptedKey = await this.keyBag.get(decryptionKeyId);
|
|
111
|
+
if (!decryptedKey) {
|
|
112
|
+
throw new errors_1.SymmetricKeyNotFoundError(decryptionKeyId);
|
|
113
|
+
}
|
|
114
|
+
symmetricKey = decryptedKey;
|
|
115
|
+
console.log(`[BaseMindooTenant] Got named key, length: ${symmetricKey.length} bytes`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
catch (error) {
|
|
119
|
+
console.error(`[BaseMindooTenant] Error getting symmetric key:`, error);
|
|
120
|
+
throw error;
|
|
121
|
+
}
|
|
122
|
+
console.log(`[BaseMindooTenant] Got symmetric key, length: ${symmetricKey.length} bytes`);
|
|
123
|
+
const subtle = this.cryptoAdapter.getSubtle();
|
|
124
|
+
// Bind getRandomValues to maintain 'this' context
|
|
125
|
+
const randomValues = this.cryptoAdapter.getRandomValues.bind(this.cryptoAdapter);
|
|
126
|
+
// Import the symmetric key
|
|
127
|
+
console.log(`[BaseMindooTenant] Importing symmetric key for AES-GCM`);
|
|
128
|
+
let cryptoKey;
|
|
129
|
+
try {
|
|
130
|
+
// Create a new Uint8Array to ensure we have a proper ArrayBuffer
|
|
131
|
+
const keyArray = new Uint8Array(symmetricKey);
|
|
132
|
+
console.log(`[BaseMindooTenant] Key buffer size: ${keyArray.buffer.byteLength} bytes`);
|
|
133
|
+
cryptoKey = await subtle.importKey("raw", keyArray.buffer, { name: "AES-GCM" }, false, // not extractable
|
|
134
|
+
["encrypt"]);
|
|
135
|
+
console.log(`[BaseMindooTenant] Successfully imported key`);
|
|
136
|
+
}
|
|
137
|
+
catch (error) {
|
|
138
|
+
console.error(`[BaseMindooTenant] Error importing key:`, error);
|
|
139
|
+
console.error(`[BaseMindooTenant] Key length: ${symmetricKey.length}, expected: 32 bytes for AES-256`);
|
|
140
|
+
throw error;
|
|
141
|
+
}
|
|
142
|
+
// Generate IV (12 bytes for AES-GCM)
|
|
143
|
+
console.log(`[BaseMindooTenant] Generating IV`);
|
|
144
|
+
let iv;
|
|
145
|
+
try {
|
|
146
|
+
const ivArray = new Uint8Array(12);
|
|
147
|
+
randomValues(ivArray);
|
|
148
|
+
iv = new Uint8Array(ivArray.buffer, ivArray.byteOffset, ivArray.byteLength);
|
|
149
|
+
console.log(`[BaseMindooTenant] Generated IV: ${iv.length} bytes`);
|
|
150
|
+
}
|
|
151
|
+
catch (error) {
|
|
152
|
+
console.error(`[BaseMindooTenant] Error generating IV:`, error);
|
|
153
|
+
throw error;
|
|
154
|
+
}
|
|
155
|
+
// Encrypt the payload
|
|
156
|
+
console.log(`[BaseMindooTenant] Encrypting payload with AES-GCM`);
|
|
157
|
+
console.log(`[BaseMindooTenant] Payload buffer size: ${payload.buffer.byteLength} bytes`);
|
|
158
|
+
console.log(`[BaseMindooTenant] Payload byteOffset: ${payload.byteOffset}, byteLength: ${payload.byteLength}`);
|
|
159
|
+
let encrypted;
|
|
160
|
+
try {
|
|
161
|
+
// Create a new Uint8Array to ensure we have a proper ArrayBuffer
|
|
162
|
+
const payloadArray = new Uint8Array(payload);
|
|
163
|
+
console.log(`[BaseMindooTenant] Using payload array: ${payloadArray.length} bytes`);
|
|
164
|
+
encrypted = await subtle.encrypt({
|
|
165
|
+
name: "AES-GCM",
|
|
166
|
+
iv: iv,
|
|
167
|
+
tagLength: 128, // 128-bit authentication tag
|
|
168
|
+
}, cryptoKey, payloadArray);
|
|
169
|
+
console.log(`[BaseMindooTenant] Successfully encrypted payload, encrypted size: ${encrypted.byteLength} bytes`);
|
|
170
|
+
}
|
|
171
|
+
catch (error) {
|
|
172
|
+
console.error(`[BaseMindooTenant] Error encrypting payload:`, error);
|
|
173
|
+
console.error(`[BaseMindooTenant] Error details:`, {
|
|
174
|
+
errorName: error instanceof Error ? error.name : typeof error,
|
|
175
|
+
errorMessage: error instanceof Error ? error.message : String(error),
|
|
176
|
+
payloadLength: payload.length,
|
|
177
|
+
keyLength: symmetricKey.length,
|
|
178
|
+
ivLength: iv.length,
|
|
179
|
+
});
|
|
180
|
+
throw error;
|
|
181
|
+
}
|
|
182
|
+
// Combine IV and encrypted data
|
|
183
|
+
// Format: IV (12 bytes) + encrypted data (includes authentication tag)
|
|
184
|
+
console.log(`[BaseMindooTenant] Combining IV and encrypted data`);
|
|
185
|
+
const encryptedArray = new Uint8Array(encrypted);
|
|
186
|
+
const result = new Uint8Array(12 + encryptedArray.length);
|
|
187
|
+
result.set(iv, 0);
|
|
188
|
+
result.set(encryptedArray, 12);
|
|
189
|
+
console.log(`[BaseMindooTenant] Encrypted payload (${payload.length} -> ${result.length} bytes)`);
|
|
190
|
+
return result;
|
|
191
|
+
}
|
|
192
|
+
async decryptPayload(encryptedPayload, decryptionKeyId) {
|
|
193
|
+
console.log(`[BaseMindooTenant] Decrypting payload with key: ${decryptionKeyId}`);
|
|
194
|
+
if (encryptedPayload.length < 12) {
|
|
195
|
+
throw new Error("Encrypted payload too short (missing IV)");
|
|
196
|
+
}
|
|
197
|
+
// Extract IV (first 12 bytes) and encrypted data (rest)
|
|
198
|
+
const iv = encryptedPayload.slice(0, 12);
|
|
199
|
+
const encryptedData = encryptedPayload.slice(12);
|
|
200
|
+
// Get the symmetric key for this key ID
|
|
201
|
+
let symmetricKey;
|
|
202
|
+
if (decryptionKeyId === "default") {
|
|
203
|
+
// Use cached tenant encryption key if available
|
|
204
|
+
if (this.decryptedTenantKeyCache) {
|
|
205
|
+
symmetricKey = this.decryptedTenantKeyCache;
|
|
206
|
+
}
|
|
207
|
+
else {
|
|
208
|
+
// Decrypt tenant encryption key
|
|
209
|
+
const decrypted = await this.decryptPrivateKey(this.tenantEncryptionKey, this.tenantEncryptionKeyPassword, "default");
|
|
210
|
+
symmetricKey = new Uint8Array(decrypted);
|
|
211
|
+
this.decryptedTenantKeyCache = symmetricKey;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
else {
|
|
215
|
+
// Get the decrypted key from KeyBag
|
|
216
|
+
const decryptedKey = await this.keyBag.get(decryptionKeyId);
|
|
217
|
+
if (!decryptedKey) {
|
|
218
|
+
throw new errors_1.SymmetricKeyNotFoundError(decryptionKeyId);
|
|
219
|
+
}
|
|
220
|
+
symmetricKey = decryptedKey;
|
|
221
|
+
}
|
|
222
|
+
const subtle = this.cryptoAdapter.getSubtle();
|
|
223
|
+
// Import the symmetric key
|
|
224
|
+
const cryptoKey = await subtle.importKey("raw", symmetricKey.buffer, { name: "AES-GCM" }, false, // not extractable
|
|
225
|
+
["decrypt"]);
|
|
226
|
+
// Decrypt the payload
|
|
227
|
+
const decrypted = await subtle.decrypt({
|
|
228
|
+
name: "AES-GCM",
|
|
229
|
+
iv: iv,
|
|
230
|
+
tagLength: 128, // 128-bit authentication tag
|
|
231
|
+
}, cryptoKey, encryptedData.buffer);
|
|
232
|
+
console.log(`[BaseMindooTenant] Decrypted payload (${encryptedPayload.length} -> ${decrypted.byteLength} bytes)`);
|
|
233
|
+
return new Uint8Array(decrypted);
|
|
234
|
+
}
|
|
235
|
+
async signPayload(payload) {
|
|
236
|
+
console.log(`[BaseMindooTenant] Signing payload`);
|
|
237
|
+
// Get the current user's signing private key (decrypted)
|
|
238
|
+
const signingKey = await this.getDecryptedSigningKey();
|
|
239
|
+
const subtle = this.cryptoAdapter.getSubtle();
|
|
240
|
+
// Sign the payload
|
|
241
|
+
const signature = await subtle.sign({
|
|
242
|
+
name: "Ed25519",
|
|
243
|
+
}, signingKey, payload.buffer);
|
|
244
|
+
console.log(`[BaseMindooTenant] Signed payload (signature: ${signature.byteLength} bytes)`);
|
|
245
|
+
return new Uint8Array(signature);
|
|
246
|
+
}
|
|
247
|
+
async signPayloadWithKey(payload, signingKeyPair, password) {
|
|
248
|
+
console.log(`[BaseMindooTenant] Signing payload with provided key`);
|
|
249
|
+
const subtle = this.cryptoAdapter.getSubtle();
|
|
250
|
+
// Decrypt the provided signing private key
|
|
251
|
+
const decryptedKeyBuffer = await this.decryptPrivateKey(signingKeyPair.privateKey, password, "signing");
|
|
252
|
+
// Import the decrypted key as an Ed25519 private key
|
|
253
|
+
const signingKey = await subtle.importKey("pkcs8", decryptedKeyBuffer, {
|
|
254
|
+
name: "Ed25519",
|
|
255
|
+
}, false, // not extractable
|
|
256
|
+
["sign"]);
|
|
257
|
+
// Sign the payload
|
|
258
|
+
const signature = await subtle.sign({
|
|
259
|
+
name: "Ed25519",
|
|
260
|
+
}, signingKey, payload.buffer);
|
|
261
|
+
console.log(`[BaseMindooTenant] Signed payload with provided key (signature: ${signature.byteLength} bytes)`);
|
|
262
|
+
return new Uint8Array(signature);
|
|
263
|
+
}
|
|
264
|
+
async verifySignature(payload, signature, publicKey) {
|
|
265
|
+
console.log(`[BaseMindooTenant] Verifying signature`);
|
|
266
|
+
const directory = await this.openDirectory();
|
|
267
|
+
const isTrusted = await directory.validatePublicSigningKey(publicKey);
|
|
268
|
+
if (!isTrusted) {
|
|
269
|
+
console.warn(`[BaseMindooTenant] Public key not trusted: ${publicKey}`);
|
|
270
|
+
return false;
|
|
271
|
+
}
|
|
272
|
+
const subtle = this.cryptoAdapter.getSubtle();
|
|
273
|
+
// Convert PEM format to ArrayBuffer
|
|
274
|
+
const publicKeyBuffer = this.pemToArrayBuffer(publicKey);
|
|
275
|
+
// Import the public key from SPKI format
|
|
276
|
+
const cryptoKey = await subtle.importKey("spki", publicKeyBuffer, {
|
|
277
|
+
name: "Ed25519",
|
|
278
|
+
}, false, // not extractable
|
|
279
|
+
["verify"]);
|
|
280
|
+
// Verify the signature
|
|
281
|
+
const isValid = await subtle.verify({
|
|
282
|
+
name: "Ed25519",
|
|
283
|
+
}, cryptoKey, signature.buffer, payload.buffer);
|
|
284
|
+
console.log(`[BaseMindooTenant] Signature verification result: ${isValid}`);
|
|
285
|
+
return isValid;
|
|
286
|
+
}
|
|
287
|
+
async validatePublicSigningKey(publicKey) {
|
|
288
|
+
console.log(`[BaseMindooTenant] Validating public signing key`);
|
|
289
|
+
// Load the directory database to check if this key belongs to a trusted user
|
|
290
|
+
const directoryDB = await this.openDB("directory");
|
|
291
|
+
// TODO: Query the directory database to check if this public key belongs to a registered,
|
|
292
|
+
// non-revoked user. For now, return true as a placeholder.
|
|
293
|
+
// The directory database should contain user records with their public signing keys.
|
|
294
|
+
// This is a placeholder - in a full implementation, we would:
|
|
295
|
+
// 1. Query the directory database for users with this public key
|
|
296
|
+
// 2. Check if the user is registered and not revoked
|
|
297
|
+
// 3. Return true only if the key belongs to a trusted user
|
|
298
|
+
return true; // TODO: Implement actual validation against directory database
|
|
299
|
+
}
|
|
300
|
+
async getCurrentUserId() {
|
|
301
|
+
// Convert PrivateUserId to PublicUserId
|
|
302
|
+
return {
|
|
303
|
+
username: this.currentUser.username,
|
|
304
|
+
administrationSignature: this.currentUser.administrationSignature,
|
|
305
|
+
userSigningPublicKey: this.currentUser.userSigningKeyPair.publicKey,
|
|
306
|
+
userEncryptionPublicKey: this.currentUser.userEncryptionKeyPair.publicKey,
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
async openDirectory() {
|
|
310
|
+
if (!this.directoryCache) {
|
|
311
|
+
this.directoryCache = new BaseMindooTenantDirectory_1.BaseMindooTenantDirectory(this);
|
|
312
|
+
}
|
|
313
|
+
return this.directoryCache;
|
|
314
|
+
}
|
|
315
|
+
async openDB(id, options) {
|
|
316
|
+
// Enforce admin-only mode for directory database - this is a security invariant
|
|
317
|
+
// The directory database must only accept entries signed by the admin key
|
|
318
|
+
const effectiveOptions = id === "directory"
|
|
319
|
+
? { ...options, adminOnlyDb: true }
|
|
320
|
+
: options ?? {};
|
|
321
|
+
// Return cached database if it exists
|
|
322
|
+
const cached = this.databaseCache.get(id);
|
|
323
|
+
if (cached) {
|
|
324
|
+
// For directory DB, verify admin-only flag matches (defensive check)
|
|
325
|
+
if (id === "directory" && !cached.isAdminOnlyDb()) {
|
|
326
|
+
throw new Error("Directory database was cached without adminOnlyDb - this should never happen");
|
|
327
|
+
}
|
|
328
|
+
return cached;
|
|
329
|
+
}
|
|
330
|
+
// Extract store options and DB-specific options
|
|
331
|
+
const { adminOnlyDb, attachmentConfig, ...storeOptions } = effectiveOptions;
|
|
332
|
+
// Create the database stores using the factory
|
|
333
|
+
const { docStore, attachmentStore } = this.storeFactory.createStore(id, storeOptions);
|
|
334
|
+
const db = new BaseMindooDB_1.BaseMindooDB(this, docStore, attachmentStore, attachmentConfig, adminOnlyDb ?? false);
|
|
335
|
+
await db.initialize();
|
|
336
|
+
// Cache the database for future use
|
|
337
|
+
this.databaseCache.set(id, db);
|
|
338
|
+
return db;
|
|
339
|
+
}
|
|
340
|
+
createDocSignerFor(signKey) {
|
|
341
|
+
console.log(`[BaseMindooTenant] Creating MindooDocSigner for signing key pair`);
|
|
342
|
+
return new MindooDocSigner_1.MindooDocSigner(this, signKey);
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* Internal method to get the decrypted signing key for the current user.
|
|
346
|
+
*/
|
|
347
|
+
async getDecryptedSigningKey() {
|
|
348
|
+
// Use cached signing key if available
|
|
349
|
+
if (this.decryptedUserSigningKeyCache) {
|
|
350
|
+
return this.decryptedUserSigningKeyCache;
|
|
351
|
+
}
|
|
352
|
+
// Decrypt the user's signing private key
|
|
353
|
+
const decryptedKeyBuffer = await this.decryptPrivateKey(this.currentUser.userSigningKeyPair.privateKey, this.currentUserPassword, "signing");
|
|
354
|
+
const subtle = this.cryptoAdapter.getSubtle();
|
|
355
|
+
// Import the decrypted key as an Ed25519 private key
|
|
356
|
+
const cryptoKey = await subtle.importKey("pkcs8", decryptedKeyBuffer, {
|
|
357
|
+
name: "Ed25519",
|
|
358
|
+
}, false, // not extractable
|
|
359
|
+
["sign"]);
|
|
360
|
+
this.decryptedUserSigningKeyCache = cryptoKey;
|
|
361
|
+
return cryptoKey;
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* Internal method to decrypt a private key using password-based key derivation.
|
|
365
|
+
* Protected so that BaseMindooTenantDirectory can access it.
|
|
366
|
+
*
|
|
367
|
+
* @param encryptedKey The encrypted private key
|
|
368
|
+
* @param password The password to decrypt the key
|
|
369
|
+
* @param saltString The salt string for key derivation (e.g., "signing", "encryption", "administration", keyId)
|
|
370
|
+
* This is combined with the encryptedKey.salt for additional security
|
|
371
|
+
* @returns The decrypted private key as ArrayBuffer
|
|
372
|
+
*/
|
|
373
|
+
async decryptPrivateKey(encryptedKey, password, saltString) {
|
|
374
|
+
console.log(`[BaseMindooTenant] decryptPrivateKey: Starting decryption with saltString: ${saltString}`);
|
|
375
|
+
console.log(`[BaseMindooTenant] decryptPrivateKey: Password length: ${password.length}`);
|
|
376
|
+
console.log(`[BaseMindooTenant] decryptPrivateKey: EncryptedKey iterations: ${encryptedKey.iterations}`);
|
|
377
|
+
const subtle = this.cryptoAdapter.getSubtle();
|
|
378
|
+
// Decode base64 strings
|
|
379
|
+
console.log(`[BaseMindooTenant] decryptPrivateKey: Decoding base64 strings`);
|
|
380
|
+
let ciphertext;
|
|
381
|
+
let iv;
|
|
382
|
+
let tag;
|
|
383
|
+
let saltBytes;
|
|
384
|
+
try {
|
|
385
|
+
ciphertext = this.base64ToUint8Array(encryptedKey.ciphertext);
|
|
386
|
+
iv = this.base64ToUint8Array(encryptedKey.iv);
|
|
387
|
+
tag = this.base64ToUint8Array(encryptedKey.tag);
|
|
388
|
+
saltBytes = this.base64ToUint8Array(encryptedKey.salt);
|
|
389
|
+
console.log(`[BaseMindooTenant] decryptPrivateKey: Decoded - ciphertext: ${ciphertext.length} bytes, iv: ${iv.length} bytes, tag: ${tag.length} bytes, salt: ${saltBytes.length} bytes`);
|
|
390
|
+
}
|
|
391
|
+
catch (error) {
|
|
392
|
+
console.error(`[BaseMindooTenant] decryptPrivateKey: Error decoding base64:`, error);
|
|
393
|
+
throw error;
|
|
394
|
+
}
|
|
395
|
+
// Derive key from password using PBKDF2
|
|
396
|
+
// Combine the stored salt bytes with the salt string for additional security
|
|
397
|
+
// This ensures different key types (signing, encryption, etc.) use different derived keys
|
|
398
|
+
// even if they share the same password
|
|
399
|
+
console.log(`[BaseMindooTenant] decryptPrivateKey: Combining salt with saltString`);
|
|
400
|
+
const saltStringBytes = new TextEncoder().encode(saltString);
|
|
401
|
+
const combinedSalt = new Uint8Array(saltBytes.length + saltStringBytes.length);
|
|
402
|
+
combinedSalt.set(saltBytes);
|
|
403
|
+
combinedSalt.set(saltStringBytes, saltBytes.length);
|
|
404
|
+
console.log(`[BaseMindooTenant] decryptPrivateKey: Combined salt length: ${combinedSalt.length} bytes`);
|
|
405
|
+
console.log(`[BaseMindooTenant] decryptPrivateKey: Importing password key for PBKDF2`);
|
|
406
|
+
let passwordKey;
|
|
407
|
+
try {
|
|
408
|
+
passwordKey = await subtle.importKey("raw", new TextEncoder().encode(password), "PBKDF2", false, ["deriveBits", "deriveKey"]);
|
|
409
|
+
console.log(`[BaseMindooTenant] decryptPrivateKey: Successfully imported password key`);
|
|
410
|
+
}
|
|
411
|
+
catch (error) {
|
|
412
|
+
console.error(`[BaseMindooTenant] decryptPrivateKey: Error importing password key:`, error);
|
|
413
|
+
throw error;
|
|
414
|
+
}
|
|
415
|
+
console.log(`[BaseMindooTenant] decryptPrivateKey: Deriving key with PBKDF2 (iterations: ${encryptedKey.iterations})`);
|
|
416
|
+
let derivedKey;
|
|
417
|
+
try {
|
|
418
|
+
derivedKey = await subtle.deriveKey({
|
|
419
|
+
name: "PBKDF2",
|
|
420
|
+
salt: combinedSalt,
|
|
421
|
+
iterations: encryptedKey.iterations,
|
|
422
|
+
hash: "SHA-256",
|
|
423
|
+
}, passwordKey, {
|
|
424
|
+
name: "AES-GCM",
|
|
425
|
+
length: 256,
|
|
426
|
+
}, false, ["decrypt"]);
|
|
427
|
+
console.log(`[BaseMindooTenant] decryptPrivateKey: Successfully derived key`);
|
|
428
|
+
}
|
|
429
|
+
catch (error) {
|
|
430
|
+
console.error(`[BaseMindooTenant] decryptPrivateKey: Error deriving key:`, error);
|
|
431
|
+
throw error;
|
|
432
|
+
}
|
|
433
|
+
// Combine ciphertext and tag (GCM authentication tag is separate)
|
|
434
|
+
// AES-GCM expects the tag to be appended to the ciphertext
|
|
435
|
+
console.log(`[BaseMindooTenant] decryptPrivateKey: Combining ciphertext and tag`);
|
|
436
|
+
const encryptedData = new Uint8Array(ciphertext.length + tag.length);
|
|
437
|
+
encryptedData.set(ciphertext);
|
|
438
|
+
encryptedData.set(tag, ciphertext.length);
|
|
439
|
+
console.log(`[BaseMindooTenant] decryptPrivateKey: Combined encrypted data length: ${encryptedData.length} bytes`);
|
|
440
|
+
// Decrypt the private key
|
|
441
|
+
console.log(`[BaseMindooTenant] decryptPrivateKey: Decrypting with AES-GCM`);
|
|
442
|
+
console.log(`[BaseMindooTenant] decryptPrivateKey: IV length: ${iv.length} bytes (expected: 12 for AES-GCM)`);
|
|
443
|
+
console.log(`[BaseMindooTenant] decryptPrivateKey: Tag length: ${tag.length} bytes (expected: 16 for 128-bit tag)`);
|
|
444
|
+
console.log(`[BaseMindooTenant] decryptPrivateKey: Ciphertext length: ${ciphertext.length} bytes`);
|
|
445
|
+
let decrypted;
|
|
446
|
+
try {
|
|
447
|
+
// Create a new ArrayBuffer to ensure we have the correct buffer without any offset issues
|
|
448
|
+
const encryptedBuffer = new Uint8Array(encryptedData).buffer;
|
|
449
|
+
console.log(`[BaseMindooTenant] decryptPrivateKey: Encrypted buffer size: ${encryptedBuffer.byteLength} bytes`);
|
|
450
|
+
decrypted = await subtle.decrypt({
|
|
451
|
+
name: "AES-GCM",
|
|
452
|
+
iv: new Uint8Array(iv), // Create a new Uint8Array to ensure proper buffer
|
|
453
|
+
tagLength: 128,
|
|
454
|
+
}, derivedKey, encryptedBuffer);
|
|
455
|
+
console.log(`[BaseMindooTenant] decryptPrivateKey: Successfully decrypted, result length: ${decrypted.byteLength} bytes`);
|
|
456
|
+
}
|
|
457
|
+
catch (error) {
|
|
458
|
+
console.error(`[BaseMindooTenant] decryptPrivateKey: Error during decryption:`, error);
|
|
459
|
+
console.error(`[BaseMindooTenant] decryptPrivateKey: Error details:`, {
|
|
460
|
+
errorName: error instanceof Error ? error.name : typeof error,
|
|
461
|
+
errorMessage: error instanceof Error ? error.message : String(error),
|
|
462
|
+
ivLength: iv.length,
|
|
463
|
+
tagLength: tag.length,
|
|
464
|
+
ciphertextLength: ciphertext.length,
|
|
465
|
+
encryptedDataLength: encryptedData.length,
|
|
466
|
+
iterations: encryptedKey.iterations,
|
|
467
|
+
saltString: saltString,
|
|
468
|
+
encryptedDataByteOffset: encryptedData.byteOffset,
|
|
469
|
+
encryptedDataByteLength: encryptedData.byteLength,
|
|
470
|
+
encryptedDataBufferLength: encryptedData.buffer.byteLength,
|
|
471
|
+
});
|
|
472
|
+
throw error;
|
|
473
|
+
}
|
|
474
|
+
return decrypted;
|
|
475
|
+
}
|
|
476
|
+
/**
|
|
477
|
+
* Helper method to convert base64 string to Uint8Array
|
|
478
|
+
* Protected so that BaseMindooTenantDirectory can access it.
|
|
479
|
+
*/
|
|
480
|
+
base64ToUint8Array(base64) {
|
|
481
|
+
const binary = atob(base64);
|
|
482
|
+
const bytes = new Uint8Array(binary.length);
|
|
483
|
+
for (let i = 0; i < binary.length; i++) {
|
|
484
|
+
bytes[i] = binary.charCodeAt(i);
|
|
485
|
+
}
|
|
486
|
+
return bytes;
|
|
487
|
+
}
|
|
488
|
+
/**
|
|
489
|
+
* Helper method to convert Uint8Array to base64 string
|
|
490
|
+
* Protected so that BaseMindooTenantDirectory can access it.
|
|
491
|
+
*/
|
|
492
|
+
uint8ArrayToBase64(bytes) {
|
|
493
|
+
let binary = "";
|
|
494
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
495
|
+
binary += String.fromCharCode(bytes[i]);
|
|
496
|
+
}
|
|
497
|
+
return btoa(binary);
|
|
498
|
+
}
|
|
499
|
+
/**
|
|
500
|
+
* Helper method to convert PEM format to ArrayBuffer
|
|
501
|
+
* Removes PEM headers/footers and decodes base64 content
|
|
502
|
+
* Public so that MindooDocSigner and other utilities can use it.
|
|
503
|
+
*/
|
|
504
|
+
pemToArrayBuffer(pem) {
|
|
505
|
+
// Remove PEM headers and footers
|
|
506
|
+
const pemHeader = "-----BEGIN PUBLIC KEY-----";
|
|
507
|
+
const pemFooter = "-----END PUBLIC KEY-----";
|
|
508
|
+
let base64 = pem
|
|
509
|
+
.replace(pemHeader, "")
|
|
510
|
+
.replace(pemFooter, "")
|
|
511
|
+
.replace(/\s/g, ""); // Remove all whitespace (newlines, spaces, etc.)
|
|
512
|
+
// Decode base64 to binary string
|
|
513
|
+
const binary = atob(base64);
|
|
514
|
+
// Convert binary string to Uint8Array
|
|
515
|
+
const bytes = new Uint8Array(binary.length);
|
|
516
|
+
for (let i = 0; i < binary.length; i++) {
|
|
517
|
+
bytes[i] = binary.charCodeAt(i);
|
|
518
|
+
}
|
|
519
|
+
return bytes.buffer;
|
|
520
|
+
}
|
|
521
|
+
/**
|
|
522
|
+
* Get the symmetric key for a given key ID.
|
|
523
|
+
* This is a protected helper method that can be used by subclasses.
|
|
524
|
+
*
|
|
525
|
+
* @param decryptionKeyId The key ID ("default" or a named key ID)
|
|
526
|
+
* @returns The decrypted symmetric key bytes
|
|
527
|
+
*/
|
|
528
|
+
async getSymmetricKey(decryptionKeyId) {
|
|
529
|
+
if (decryptionKeyId === "default") {
|
|
530
|
+
// Use cached tenant encryption key if available
|
|
531
|
+
if (this.decryptedTenantKeyCache) {
|
|
532
|
+
return this.decryptedTenantKeyCache;
|
|
533
|
+
}
|
|
534
|
+
// Decrypt tenant encryption key
|
|
535
|
+
const decrypted = await this.decryptPrivateKey(this.tenantEncryptionKey, this.tenantEncryptionKeyPassword, "default");
|
|
536
|
+
const symmetricKey = new Uint8Array(decrypted);
|
|
537
|
+
this.decryptedTenantKeyCache = symmetricKey;
|
|
538
|
+
return symmetricKey;
|
|
539
|
+
}
|
|
540
|
+
else {
|
|
541
|
+
// Get the decrypted key from KeyBag
|
|
542
|
+
const decryptedKey = await this.keyBag.get(decryptionKeyId);
|
|
543
|
+
if (!decryptedKey) {
|
|
544
|
+
throw new errors_1.SymmetricKeyNotFoundError(decryptionKeyId);
|
|
545
|
+
}
|
|
546
|
+
return decryptedKey;
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
/**
|
|
550
|
+
* Configuration method that determines whether to use deterministic encryption for attachments.
|
|
551
|
+
* Subclasses can override this to change the default behavior.
|
|
552
|
+
*
|
|
553
|
+
* When true (default):
|
|
554
|
+
* - Same plaintext + same key = same ciphertext (enables deduplication)
|
|
555
|
+
* - Reveals when identical content is stored
|
|
556
|
+
*
|
|
557
|
+
* When false:
|
|
558
|
+
* - Each encryption produces unique ciphertext
|
|
559
|
+
* - No deduplication possible
|
|
560
|
+
* - More secure for sensitive content
|
|
561
|
+
*
|
|
562
|
+
* @returns True to use deterministic encryption, false for random IV
|
|
563
|
+
*/
|
|
564
|
+
usesDeterministicAttachmentEncryption() {
|
|
565
|
+
return true; // Default: enable deduplication
|
|
566
|
+
}
|
|
567
|
+
/**
|
|
568
|
+
* Encrypt an attachment payload with the appropriate encryption mode.
|
|
569
|
+
* Uses mode prefix format: [mode byte][IV (12 bytes)][encrypted data + tag]
|
|
570
|
+
*
|
|
571
|
+
* For deterministic mode (0x01), the IV is derived from SHA-256(plaintext)[:12]
|
|
572
|
+
* For random mode (0x00), the IV is randomly generated
|
|
573
|
+
*
|
|
574
|
+
* @param payload The plaintext attachment data
|
|
575
|
+
* @param decryptionKeyId The key ID for encryption
|
|
576
|
+
* @returns The encrypted payload with mode prefix
|
|
577
|
+
*/
|
|
578
|
+
async encryptAttachmentPayload(payload, decryptionKeyId) {
|
|
579
|
+
console.log(`[BaseMindooTenant] Encrypting attachment payload with key: ${decryptionKeyId}`);
|
|
580
|
+
const symmetricKey = await this.getSymmetricKey(decryptionKeyId);
|
|
581
|
+
const subtle = this.cryptoAdapter.getSubtle();
|
|
582
|
+
const randomValues = this.cryptoAdapter.getRandomValues.bind(this.cryptoAdapter);
|
|
583
|
+
// Import the symmetric key
|
|
584
|
+
const keyArray = new Uint8Array(symmetricKey);
|
|
585
|
+
const cryptoKey = await subtle.importKey("raw", keyArray.buffer, { name: "AES-GCM" }, false, ["encrypt"]);
|
|
586
|
+
// Determine encryption mode and generate IV accordingly
|
|
587
|
+
const useDeterministic = this.usesDeterministicAttachmentEncryption();
|
|
588
|
+
const mode = useDeterministic
|
|
589
|
+
? BaseMindooTenant.ENCRYPTION_MODE_DETERMINISTIC
|
|
590
|
+
: BaseMindooTenant.ENCRYPTION_MODE_RANDOM;
|
|
591
|
+
let iv;
|
|
592
|
+
if (useDeterministic) {
|
|
593
|
+
// Deterministic IV: derive from plaintext hash
|
|
594
|
+
// IV = SHA-256(plaintext)[:12]
|
|
595
|
+
const payloadBuffer = payload.buffer.slice(payload.byteOffset, payload.byteOffset + payload.byteLength);
|
|
596
|
+
const hashBuffer = await subtle.digest("SHA-256", payloadBuffer);
|
|
597
|
+
iv = new Uint8Array(hashBuffer).slice(0, 12);
|
|
598
|
+
console.log(`[BaseMindooTenant] Using deterministic IV derived from content hash`);
|
|
599
|
+
}
|
|
600
|
+
else {
|
|
601
|
+
// Random IV
|
|
602
|
+
iv = new Uint8Array(12);
|
|
603
|
+
randomValues(iv);
|
|
604
|
+
console.log(`[BaseMindooTenant] Using random IV`);
|
|
605
|
+
}
|
|
606
|
+
// Encrypt the payload
|
|
607
|
+
const payloadArray = new Uint8Array(payload);
|
|
608
|
+
const encrypted = await subtle.encrypt({
|
|
609
|
+
name: "AES-GCM",
|
|
610
|
+
iv: iv,
|
|
611
|
+
tagLength: 128,
|
|
612
|
+
}, cryptoKey, payloadArray);
|
|
613
|
+
// Combine: mode byte (1) + IV (12) + encrypted data
|
|
614
|
+
const encryptedArray = new Uint8Array(encrypted);
|
|
615
|
+
const result = new Uint8Array(1 + 12 + encryptedArray.length);
|
|
616
|
+
result[0] = mode;
|
|
617
|
+
result.set(iv, 1);
|
|
618
|
+
result.set(encryptedArray, 13);
|
|
619
|
+
console.log(`[BaseMindooTenant] Encrypted attachment (mode=${mode}, ${payload.length} -> ${result.length} bytes)`);
|
|
620
|
+
return result;
|
|
621
|
+
}
|
|
622
|
+
/**
|
|
623
|
+
* Decrypt an attachment payload that was encrypted with encryptAttachmentPayload.
|
|
624
|
+
* Reads the mode prefix to determine which decryption method to use.
|
|
625
|
+
*
|
|
626
|
+
* @param encryptedPayload The encrypted payload with mode prefix
|
|
627
|
+
* @param decryptionKeyId The key ID for decryption
|
|
628
|
+
* @returns The decrypted plaintext
|
|
629
|
+
*/
|
|
630
|
+
async decryptAttachmentPayload(encryptedPayload, decryptionKeyId) {
|
|
631
|
+
console.log(`[BaseMindooTenant] Decrypting attachment payload with key: ${decryptionKeyId}`);
|
|
632
|
+
if (encryptedPayload.length < 13) {
|
|
633
|
+
throw new Error("Invalid encrypted attachment payload: too short");
|
|
634
|
+
}
|
|
635
|
+
// Read mode byte
|
|
636
|
+
const mode = encryptedPayload[0];
|
|
637
|
+
if (mode !== BaseMindooTenant.ENCRYPTION_MODE_RANDOM &&
|
|
638
|
+
mode !== BaseMindooTenant.ENCRYPTION_MODE_DETERMINISTIC) {
|
|
639
|
+
throw new Error(`Invalid encryption mode: ${mode}`);
|
|
640
|
+
}
|
|
641
|
+
console.log(`[BaseMindooTenant] Decrypting attachment with mode=${mode}`);
|
|
642
|
+
// Extract IV (bytes 1-12)
|
|
643
|
+
const iv = encryptedPayload.slice(1, 13);
|
|
644
|
+
// Extract ciphertext (bytes 13 onwards)
|
|
645
|
+
const ciphertext = encryptedPayload.slice(13);
|
|
646
|
+
const symmetricKey = await this.getSymmetricKey(decryptionKeyId);
|
|
647
|
+
const subtle = this.cryptoAdapter.getSubtle();
|
|
648
|
+
// Import the symmetric key
|
|
649
|
+
const keyArray = new Uint8Array(symmetricKey);
|
|
650
|
+
const cryptoKey = await subtle.importKey("raw", keyArray.buffer, { name: "AES-GCM" }, false, ["decrypt"]);
|
|
651
|
+
// Decrypt
|
|
652
|
+
const decrypted = await subtle.decrypt({
|
|
653
|
+
name: "AES-GCM",
|
|
654
|
+
iv: iv,
|
|
655
|
+
tagLength: 128,
|
|
656
|
+
}, cryptoKey, ciphertext);
|
|
657
|
+
const result = new Uint8Array(decrypted);
|
|
658
|
+
console.log(`[BaseMindooTenant] Decrypted attachment (${encryptedPayload.length} -> ${result.length} bytes)`);
|
|
659
|
+
return result;
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
exports.BaseMindooTenant = BaseMindooTenant;
|
|
663
|
+
// ============================================================================
|
|
664
|
+
// Attachment Encryption Methods
|
|
665
|
+
// ============================================================================
|
|
666
|
+
/**
|
|
667
|
+
* Encryption mode byte values for attachment payloads.
|
|
668
|
+
* 0x00 = Random IV (more secure, no deduplication)
|
|
669
|
+
* 0x01 = Deterministic IV (enables deduplication, reveals identical content)
|
|
670
|
+
*/
|
|
671
|
+
BaseMindooTenant.ENCRYPTION_MODE_RANDOM = 0x00;
|
|
672
|
+
BaseMindooTenant.ENCRYPTION_MODE_DETERMINISTIC = 0x01;
|
|
673
|
+
//# sourceMappingURL=BaseMindooTenant.js.map
|