@brightchain/brightchain-api-lib 0.14.0 → 0.15.0
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/package.json +5 -5
- package/src/index.d.ts +3 -0
- package/src/index.d.ts.map +1 -1
- package/src/index.js +5 -0
- package/src/index.js.map +1 -1
- package/src/lib/application.d.ts +1 -0
- package/src/lib/application.d.ts.map +1 -1
- package/src/lib/application.js +23 -0
- package/src/lib/application.js.map +1 -1
- package/src/lib/auth/aclEnforcedAvailability.d.ts +57 -0
- package/src/lib/auth/aclEnforcedAvailability.d.ts.map +1 -0
- package/src/lib/auth/aclEnforcedAvailability.js +87 -0
- package/src/lib/auth/aclEnforcedAvailability.js.map +1 -0
- package/src/lib/auth/aclEnforcedBlockStore.d.ts +66 -0
- package/src/lib/auth/aclEnforcedBlockStore.d.ts.map +1 -0
- package/src/lib/auth/aclEnforcedBlockStore.js +83 -0
- package/src/lib/auth/aclEnforcedBlockStore.js.map +1 -0
- package/src/lib/auth/ecdsaNodeAuthenticator.d.ts +46 -0
- package/src/lib/auth/ecdsaNodeAuthenticator.d.ts.map +1 -0
- package/src/lib/auth/ecdsaNodeAuthenticator.js +110 -0
- package/src/lib/auth/ecdsaNodeAuthenticator.js.map +1 -0
- package/src/lib/auth/index.d.ts +7 -0
- package/src/lib/auth/index.d.ts.map +1 -0
- package/src/lib/auth/index.js +13 -0
- package/src/lib/auth/index.js.map +1 -0
- package/src/lib/auth/poolAclBootstrap.d.ts +36 -0
- package/src/lib/auth/poolAclBootstrap.d.ts.map +1 -0
- package/src/lib/auth/poolAclBootstrap.js +64 -0
- package/src/lib/auth/poolAclBootstrap.js.map +1 -0
- package/src/lib/auth/poolAclStore.d.ts +77 -0
- package/src/lib/auth/poolAclStore.d.ts.map +1 -0
- package/src/lib/auth/poolAclStore.js +189 -0
- package/src/lib/auth/poolAclStore.js.map +1 -0
- package/src/lib/auth/poolAclUpdater.d.ts +79 -0
- package/src/lib/auth/poolAclUpdater.d.ts.map +1 -0
- package/src/lib/auth/poolAclUpdater.js +144 -0
- package/src/lib/auth/poolAclUpdater.js.map +1 -0
- package/src/lib/availability/availabilityService.d.ts +2 -2
- package/src/lib/availability/availabilityService.d.ts.map +1 -1
- package/src/lib/availability/availabilityService.js +12 -5
- package/src/lib/availability/availabilityService.js.map +1 -1
- package/src/lib/availability/blockRegistry.d.ts +45 -3
- package/src/lib/availability/blockRegistry.d.ts.map +1 -1
- package/src/lib/availability/blockRegistry.js +123 -5
- package/src/lib/availability/blockRegistry.js.map +1 -1
- package/src/lib/availability/discoveryProtocol.d.ts +30 -1
- package/src/lib/availability/discoveryProtocol.d.ts.map +1 -1
- package/src/lib/availability/discoveryProtocol.js +76 -0
- package/src/lib/availability/discoveryProtocol.js.map +1 -1
- package/src/lib/availability/gossipService.d.ts +45 -6
- package/src/lib/availability/gossipService.d.ts.map +1 -1
- package/src/lib/availability/gossipService.js +177 -5
- package/src/lib/availability/gossipService.js.map +1 -1
- package/src/lib/availability/reconciliationService.d.ts +88 -1
- package/src/lib/availability/reconciliationService.d.ts.map +1 -1
- package/src/lib/availability/reconciliationService.js +246 -48
- package/src/lib/availability/reconciliationService.js.map +1 -1
- package/src/lib/blockFetch/blockFetcher.d.ts +100 -0
- package/src/lib/blockFetch/blockFetcher.d.ts.map +1 -0
- package/src/lib/blockFetch/blockFetcher.js +279 -0
- package/src/lib/blockFetch/blockFetcher.js.map +1 -0
- package/src/lib/blockFetch/fetchQueue.d.ts +88 -0
- package/src/lib/blockFetch/fetchQueue.d.ts.map +1 -0
- package/src/lib/blockFetch/fetchQueue.js +204 -0
- package/src/lib/blockFetch/fetchQueue.js.map +1 -0
- package/src/lib/blockFetch/httpBlockFetchTransport.d.ts +65 -0
- package/src/lib/blockFetch/httpBlockFetchTransport.d.ts.map +1 -0
- package/src/lib/blockFetch/httpBlockFetchTransport.js +104 -0
- package/src/lib/blockFetch/httpBlockFetchTransport.js.map +1 -0
- package/src/lib/blockFetch/index.d.ts +10 -0
- package/src/lib/blockFetch/index.d.ts.map +1 -0
- package/src/lib/blockFetch/index.js +13 -0
- package/src/lib/blockFetch/index.js.map +1 -0
- package/src/lib/controllers/api/brightpass.d.ts +72 -0
- package/src/lib/controllers/api/brightpass.d.ts.map +1 -0
- package/src/lib/controllers/api/brightpass.js +577 -0
- package/src/lib/controllers/api/brightpass.js.map +1 -0
- package/src/lib/controllers/api/channels.d.ts +122 -0
- package/src/lib/controllers/api/channels.d.ts.map +1 -0
- package/src/lib/controllers/api/channels.js +701 -0
- package/src/lib/controllers/api/channels.js.map +1 -0
- package/src/lib/controllers/api/conversations.d.ts +89 -0
- package/src/lib/controllers/api/conversations.d.ts.map +1 -0
- package/src/lib/controllers/api/conversations.js +259 -0
- package/src/lib/controllers/api/conversations.js.map +1 -0
- package/src/lib/controllers/api/emails.d.ts +122 -0
- package/src/lib/controllers/api/emails.d.ts.map +1 -0
- package/src/lib/controllers/api/emails.js +494 -0
- package/src/lib/controllers/api/emails.js.map +1 -0
- package/src/lib/controllers/api/explodingMessages.d.ts +79 -0
- package/src/lib/controllers/api/explodingMessages.d.ts.map +1 -0
- package/src/lib/controllers/api/explodingMessages.js +378 -0
- package/src/lib/controllers/api/explodingMessages.js.map +1 -0
- package/src/lib/controllers/api/groups.d.ts +94 -0
- package/src/lib/controllers/api/groups.d.ts.map +1 -0
- package/src/lib/controllers/api/groups.js +484 -0
- package/src/lib/controllers/api/groups.js.map +1 -0
- package/src/lib/controllers/api/index.d.ts +6 -0
- package/src/lib/controllers/api/index.d.ts.map +1 -1
- package/src/lib/controllers/api/index.js +6 -0
- package/src/lib/controllers/api/index.js.map +1 -1
- package/src/lib/controllers/api/messages.d.ts.map +1 -1
- package/src/lib/controllers/api/messages.js +2 -1
- package/src/lib/controllers/api/messages.js.map +1 -1
- package/src/lib/controllers/api/sync.d.ts +38 -2
- package/src/lib/controllers/api/sync.d.ts.map +1 -1
- package/src/lib/controllers/api/sync.js +89 -0
- package/src/lib/controllers/api/sync.js.map +1 -1
- package/src/lib/controllers/crypto/gitController.d.ts +70 -0
- package/src/lib/controllers/crypto/gitController.d.ts.map +1 -0
- package/src/lib/controllers/crypto/gitController.js +306 -0
- package/src/lib/controllers/crypto/gitController.js.map +1 -0
- package/src/lib/controllers/crypto/index.d.ts +3 -0
- package/src/lib/controllers/crypto/index.d.ts.map +1 -0
- package/src/lib/controllers/crypto/index.js +6 -0
- package/src/lib/controllers/crypto/index.js.map +1 -0
- package/src/lib/controllers/crypto/walletController.d.ts +64 -0
- package/src/lib/controllers/crypto/walletController.d.ts.map +1 -0
- package/src/lib/controllers/crypto/walletController.js +260 -0
- package/src/lib/controllers/crypto/walletController.js.map +1 -0
- package/src/lib/controllers/identity/deviceController.d.ts +96 -0
- package/src/lib/controllers/identity/deviceController.d.ts.map +1 -0
- package/src/lib/controllers/identity/deviceController.js +355 -0
- package/src/lib/controllers/identity/deviceController.js.map +1 -0
- package/src/lib/controllers/identity/directoryController.d.ts +75 -0
- package/src/lib/controllers/identity/directoryController.d.ts.map +1 -0
- package/src/lib/controllers/identity/directoryController.js +288 -0
- package/src/lib/controllers/identity/directoryController.js.map +1 -0
- package/src/lib/controllers/identity/identityProofController.d.ts +94 -0
- package/src/lib/controllers/identity/identityProofController.d.ts.map +1 -0
- package/src/lib/controllers/identity/identityProofController.js +454 -0
- package/src/lib/controllers/identity/identityProofController.js.map +1 -0
- package/src/lib/controllers/identity/index.d.ts +4 -0
- package/src/lib/controllers/identity/index.d.ts.map +1 -0
- package/src/lib/controllers/identity/index.js +7 -0
- package/src/lib/controllers/identity/index.js.map +1 -0
- package/src/lib/controllers/index.d.ts +2 -0
- package/src/lib/controllers/index.d.ts.map +1 -1
- package/src/lib/controllers/index.js +2 -0
- package/src/lib/controllers/index.js.map +1 -1
- package/src/lib/encryption/encryptedMetadataService.d.ts +87 -0
- package/src/lib/encryption/encryptedMetadataService.d.ts.map +1 -0
- package/src/lib/encryption/encryptedMetadataService.js +224 -0
- package/src/lib/encryption/encryptedMetadataService.js.map +1 -0
- package/src/lib/encryption/encryptionAwareReplication.d.ts +76 -0
- package/src/lib/encryption/encryptionAwareReplication.d.ts.map +1 -0
- package/src/lib/encryption/encryptionAwareReplication.js +116 -0
- package/src/lib/encryption/encryptionAwareReplication.js.map +1 -0
- package/src/lib/encryption/errors.d.ts +49 -0
- package/src/lib/encryption/errors.d.ts.map +1 -0
- package/src/lib/encryption/errors.js +80 -0
- package/src/lib/encryption/errors.js.map +1 -0
- package/src/lib/encryption/index.d.ts +6 -0
- package/src/lib/encryption/index.d.ts.map +1 -0
- package/src/lib/encryption/index.js +9 -0
- package/src/lib/encryption/index.js.map +1 -0
- package/src/lib/encryption/poolEncryptionService.d.ts +94 -0
- package/src/lib/encryption/poolEncryptionService.d.ts.map +1 -0
- package/src/lib/encryption/poolEncryptionService.js +252 -0
- package/src/lib/encryption/poolEncryptionService.js.map +1 -0
- package/src/lib/encryption/poolKeyManager.d.ts +82 -0
- package/src/lib/encryption/poolKeyManager.d.ts.map +1 -0
- package/src/lib/encryption/poolKeyManager.js +156 -0
- package/src/lib/encryption/poolKeyManager.js.map +1 -0
- package/src/lib/environment.d.ts +3 -0
- package/src/lib/environment.d.ts.map +1 -1
- package/src/lib/environment.js +5 -0
- package/src/lib/environment.js.map +1 -1
- package/src/lib/interfaces/environment.d.ts +7 -1
- package/src/lib/interfaces/environment.d.ts.map +1 -1
- package/src/lib/interfaces/index.d.ts +0 -1
- package/src/lib/interfaces/index.d.ts.map +1 -1
- package/src/lib/interfaces/requests/getBlockDataRequest.d.ts +12 -0
- package/src/lib/interfaces/requests/getBlockDataRequest.d.ts.map +1 -0
- package/src/lib/interfaces/{blockStore.js → requests/getBlockDataRequest.js} +1 -1
- package/src/lib/interfaces/requests/getBlockDataRequest.js.map +1 -0
- package/src/lib/interfaces/requests/index.d.ts +1 -0
- package/src/lib/interfaces/requests/index.d.ts.map +1 -1
- package/src/lib/routers/api.d.ts +54 -1
- package/src/lib/routers/api.d.ts.map +1 -1
- package/src/lib/routers/api.js +77 -0
- package/src/lib/routers/api.js.map +1 -1
- package/src/lib/services/blockStore.d.ts +5 -2
- package/src/lib/services/blockStore.d.ts.map +1 -1
- package/src/lib/services/blockStore.js +4 -0
- package/src/lib/services/blockStore.js.map +1 -1
- package/src/lib/services/brightpass/auditLogger.d.ts +77 -0
- package/src/lib/services/brightpass/auditLogger.d.ts.map +1 -0
- package/src/lib/services/brightpass/auditLogger.js +184 -0
- package/src/lib/services/brightpass/auditLogger.js.map +1 -0
- package/src/lib/services/brightpass/vaultEncryption.d.ts +82 -0
- package/src/lib/services/brightpass/vaultEncryption.d.ts.map +1 -0
- package/src/lib/services/brightpass/vaultEncryption.js +144 -0
- package/src/lib/services/brightpass/vaultEncryption.js.map +1 -0
- package/src/lib/services/brightpass.d.ts +294 -0
- package/src/lib/services/brightpass.d.ts.map +1 -0
- package/src/lib/services/brightpass.js +1260 -0
- package/src/lib/services/brightpass.js.map +1 -0
- package/src/lib/services/eventNotificationSystem.d.ts +69 -3
- package/src/lib/services/eventNotificationSystem.d.ts.map +1 -1
- package/src/lib/services/eventNotificationSystem.js +200 -0
- package/src/lib/services/eventNotificationSystem.js.map +1 -1
- package/src/lib/services/expirationScheduler.d.ts +90 -0
- package/src/lib/services/expirationScheduler.d.ts.map +1 -0
- package/src/lib/services/expirationScheduler.js +131 -0
- package/src/lib/services/expirationScheduler.js.map +1 -0
- package/src/lib/services/fecUsageExample.d.ts +2 -2
- package/src/lib/services/index.d.ts +2 -0
- package/src/lib/services/index.d.ts.map +1 -1
- package/src/lib/services/index.js +2 -0
- package/src/lib/services/index.js.map +1 -1
- package/src/lib/services/paginationService.d.ts +18 -0
- package/src/lib/services/paginationService.d.ts.map +1 -0
- package/src/lib/services/paginationService.js +32 -0
- package/src/lib/services/paginationService.js.map +1 -0
- package/src/lib/services/presenceService.d.ts +76 -0
- package/src/lib/services/presenceService.d.ts.map +1 -0
- package/src/lib/services/presenceService.js +143 -0
- package/src/lib/services/presenceService.js.map +1 -0
- package/src/lib/services/wireConversationPromotion.d.ts +23 -0
- package/src/lib/services/wireConversationPromotion.d.ts.map +1 -0
- package/src/lib/services/wireConversationPromotion.js +26 -0
- package/src/lib/services/wireConversationPromotion.js.map +1 -0
- package/src/lib/stores/availabilityAwareBlockStore.d.ts +115 -10
- package/src/lib/stores/availabilityAwareBlockStore.d.ts.map +1 -1
- package/src/lib/stores/availabilityAwareBlockStore.js +267 -23
- package/src/lib/stores/availabilityAwareBlockStore.js.map +1 -1
- package/src/lib/stores/diskBlockAsyncStore.d.ts +81 -2
- package/src/lib/stores/diskBlockAsyncStore.d.ts.map +1 -1
- package/src/lib/stores/diskBlockAsyncStore.js +297 -10
- package/src/lib/stores/diskBlockAsyncStore.js.map +1 -1
- package/src/lib/utils/communicationValidation.d.ts +44 -0
- package/src/lib/utils/communicationValidation.d.ts.map +1 -0
- package/src/lib/utils/communicationValidation.js +291 -0
- package/src/lib/utils/communicationValidation.js.map +1 -0
- package/src/lib/utils/emailValidation.d.ts +19 -0
- package/src/lib/utils/emailValidation.d.ts.map +1 -0
- package/src/lib/utils/emailValidation.js +232 -0
- package/src/lib/utils/emailValidation.js.map +1 -0
- package/src/lib/interfaces/blockStore.d.ts +0 -7
- package/src/lib/interfaces/blockStore.d.ts.map +0 -1
- package/src/lib/interfaces/blockStore.js.map +0 -1
|
@@ -0,0 +1,1260 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* BrightPassService — core vault operations for the BrightPass password manager.
|
|
4
|
+
*
|
|
5
|
+
* Manages vault lifecycle (create, open, list, delete), entry CRUD,
|
|
6
|
+
* search, sharing, emergency access, and import operations.
|
|
7
|
+
*
|
|
8
|
+
* Uses IBlockStore for persistent storage, VCBLService for VCBL operations,
|
|
9
|
+
* BlockService for encryption, and maintains a lightweight in-memory index
|
|
10
|
+
* for quick lookups.
|
|
11
|
+
*
|
|
12
|
+
* Requirements: 1.1–1.8, 2.1–2.9, 3.1–3.5, 4.1–4.4
|
|
13
|
+
*/
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.BrightPassService = exports.EmergencyAccessError = exports.EntryNotFoundError = exports.VaultConflictError = exports.VaultAuthenticationError = exports.VaultNotFoundError = void 0;
|
|
16
|
+
const tslib_1 = require("tslib");
|
|
17
|
+
const brightchain_lib_1 = require("@brightchain/brightchain-lib");
|
|
18
|
+
const uuid_1 = require("uuid");
|
|
19
|
+
const secretsModule = tslib_1.__importStar(require("@digitaldefiance/secrets"));
|
|
20
|
+
const bcrypt = tslib_1.__importStar(require("bcrypt"));
|
|
21
|
+
const bip39 = tslib_1.__importStar(require("bip39"));
|
|
22
|
+
const crypto = tslib_1.__importStar(require("crypto"));
|
|
23
|
+
const auditLogger_1 = require("./brightpass/auditLogger");
|
|
24
|
+
const vaultEncryption_1 = require("./brightpass/vaultEncryption");
|
|
25
|
+
// Handle both ESM default export and CommonJS module.exports patterns
|
|
26
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
27
|
+
const secrets = secretsModule.default || secretsModule;
|
|
28
|
+
/**
|
|
29
|
+
* Errors specific to BrightPass operations
|
|
30
|
+
*/
|
|
31
|
+
class VaultNotFoundError extends Error {
|
|
32
|
+
constructor(vaultId) {
|
|
33
|
+
super(`Vault not found: ${vaultId}`);
|
|
34
|
+
this.name = 'VaultNotFoundError';
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
exports.VaultNotFoundError = VaultNotFoundError;
|
|
38
|
+
class VaultAuthenticationError extends Error {
|
|
39
|
+
constructor() {
|
|
40
|
+
super('Vault authentication failed');
|
|
41
|
+
this.name = 'VaultAuthenticationError';
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
exports.VaultAuthenticationError = VaultAuthenticationError;
|
|
45
|
+
class VaultConflictError extends Error {
|
|
46
|
+
constructor(name) {
|
|
47
|
+
super(`Vault with name "${name}" already exists`);
|
|
48
|
+
this.name = 'VaultConflictError';
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
exports.VaultConflictError = VaultConflictError;
|
|
52
|
+
class EntryNotFoundError extends Error {
|
|
53
|
+
constructor(entryId) {
|
|
54
|
+
super(`Entry not found: ${entryId}`);
|
|
55
|
+
this.name = 'EntryNotFoundError';
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
exports.EntryNotFoundError = EntryNotFoundError;
|
|
59
|
+
class EmergencyAccessError extends Error {
|
|
60
|
+
constructor(message) {
|
|
61
|
+
super(message);
|
|
62
|
+
this.name = 'EmergencyAccessError';
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
exports.EmergencyAccessError = EmergencyAccessError;
|
|
66
|
+
class BrightPassService {
|
|
67
|
+
/**
|
|
68
|
+
* Create a new BrightPassService.
|
|
69
|
+
*
|
|
70
|
+
* @param blockStore - Block store for persistent storage (defaults to MemoryBlockStore)
|
|
71
|
+
* @param vcblService - VCBL service for vault operations
|
|
72
|
+
* @param blockService - Block service for encryption
|
|
73
|
+
* @param member - Member for block creation
|
|
74
|
+
*
|
|
75
|
+
* Requirements: 1.1, 1.5
|
|
76
|
+
*/
|
|
77
|
+
constructor(blockStore, vcblService, blockService, member) {
|
|
78
|
+
/** vaultId → StoredVault for vault data management */
|
|
79
|
+
this.vaults = new Map();
|
|
80
|
+
/** vaultId → VaultIndexEntry for lightweight lookups */
|
|
81
|
+
this.vaultIndex = new Map();
|
|
82
|
+
/** memberId → Set<vaultId> for quick listing */
|
|
83
|
+
this.memberVaults = new Map();
|
|
84
|
+
// Default to MemoryBlockStore if not provided (for backward compatibility)
|
|
85
|
+
this.blockStore = blockStore ?? new brightchain_lib_1.MemoryBlockStore(brightchain_lib_1.BlockSize.Small);
|
|
86
|
+
// Store injected services
|
|
87
|
+
this.vcblService = vcblService;
|
|
88
|
+
this.blockService = blockService ?? new brightchain_lib_1.BlockService();
|
|
89
|
+
this.member = member;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Hash a master password using bcrypt for secure storage and comparison.
|
|
93
|
+
* Uses bcrypt with 12 rounds for strong protection against brute-force attacks.
|
|
94
|
+
*
|
|
95
|
+
* Security: bcrypt includes salt automatically and is designed to be slow,
|
|
96
|
+
* making brute-force attacks computationally expensive.
|
|
97
|
+
*/
|
|
98
|
+
async hashMasterPasswordAsync(password) {
|
|
99
|
+
return bcrypt.hash(password, BrightPassService.BCRYPT_ROUNDS);
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Verify a master password against a stored bcrypt hash.
|
|
103
|
+
* Uses constant-time comparison internally via bcrypt.compare().
|
|
104
|
+
*/
|
|
105
|
+
async verifyMasterPassword(password, storedHash) {
|
|
106
|
+
return bcrypt.compare(password, storedHash);
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Generate a new BIP39 mnemonic and seed for a vault.
|
|
110
|
+
* Each vault has its own independent mnemonic that can be cycled/regenerated.
|
|
111
|
+
*
|
|
112
|
+
* Security: Uses 256 bits of entropy for a 24-word mnemonic.
|
|
113
|
+
* The mnemonic should be stored encrypted and can be backed up separately.
|
|
114
|
+
*
|
|
115
|
+
* @returns Object containing the mnemonic (24 words) and derived seed (64 bytes)
|
|
116
|
+
*/
|
|
117
|
+
generateVaultBip39() {
|
|
118
|
+
// Generate 256 bits of entropy for a 24-word mnemonic
|
|
119
|
+
const mnemonic = bip39.generateMnemonic(256);
|
|
120
|
+
// Derive 64-byte seed from mnemonic (no passphrase - master password used in key derivation)
|
|
121
|
+
const seedBuffer = bip39.mnemonicToSeedSync(mnemonic);
|
|
122
|
+
return {
|
|
123
|
+
mnemonic,
|
|
124
|
+
seed: new Uint8Array(seedBuffer),
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Regenerate a vault's BIP39 seed (key rotation).
|
|
129
|
+
* This creates a new mnemonic and re-encrypts all entries with the new key.
|
|
130
|
+
*
|
|
131
|
+
* Security: This is a critical operation that should be used when:
|
|
132
|
+
* - A share has been revoked and the vault needs re-keying
|
|
133
|
+
* - The vault owner suspects key compromise
|
|
134
|
+
* - Periodic key rotation policy requires it
|
|
135
|
+
*
|
|
136
|
+
* @param vaultId - The vault to regenerate
|
|
137
|
+
* @param masterPassword - Current master password for verification
|
|
138
|
+
* @returns The new mnemonic (should be backed up by the user)
|
|
139
|
+
*/
|
|
140
|
+
async regenerateVaultSeed(vaultId, masterPassword) {
|
|
141
|
+
const vault = this.getVaultOrThrow(vaultId);
|
|
142
|
+
// Verify master password
|
|
143
|
+
const passwordValid = await this.verifyMasterPassword(masterPassword, vault.masterPasswordHash);
|
|
144
|
+
if (!passwordValid) {
|
|
145
|
+
throw new VaultAuthenticationError();
|
|
146
|
+
}
|
|
147
|
+
// Generate new BIP39 mnemonic and seed
|
|
148
|
+
const { mnemonic: newMnemonic, seed: newSeed } = this.generateVaultBip39();
|
|
149
|
+
// Derive new vault key
|
|
150
|
+
const newVaultKey = brightchain_lib_1.VaultKeyDerivation.deriveVaultKey(newSeed, masterPassword, vaultId);
|
|
151
|
+
// Re-encrypt all entries with new key
|
|
152
|
+
const oldVaultKey = vault.vaultKey;
|
|
153
|
+
for (const [entryId, encryptedEntry] of vault.entries) {
|
|
154
|
+
const decrypted = vaultEncryption_1.VaultEncryption.decryptString(oldVaultKey, encryptedEntry);
|
|
155
|
+
const reEncrypted = vaultEncryption_1.VaultEncryption.encryptString(newVaultKey, decrypted);
|
|
156
|
+
vault.entries.set(entryId, reEncrypted);
|
|
157
|
+
}
|
|
158
|
+
// Update vault with new seed and key
|
|
159
|
+
vault.vaultMnemonic = newMnemonic;
|
|
160
|
+
vault.vaultSeed = newSeed;
|
|
161
|
+
vault.vaultKey = newVaultKey;
|
|
162
|
+
vault.metadata.updatedAt = new Date();
|
|
163
|
+
// Update index
|
|
164
|
+
const indexEntry = this.vaultIndex.get(vaultId);
|
|
165
|
+
if (indexEntry) {
|
|
166
|
+
indexEntry.vaultMnemonic = newMnemonic;
|
|
167
|
+
indexEntry.vaultSeed = newSeed;
|
|
168
|
+
indexEntry.vaultKey = newVaultKey;
|
|
169
|
+
indexEntry.updatedAt = vault.metadata.updatedAt;
|
|
170
|
+
}
|
|
171
|
+
// Invalidate any existing emergency shares (they used the old key)
|
|
172
|
+
if (vault.emergencyConfig) {
|
|
173
|
+
vault.emergencyConfig = undefined;
|
|
174
|
+
vault.emergencyShares = undefined;
|
|
175
|
+
}
|
|
176
|
+
await vault.auditLogger.log({
|
|
177
|
+
id: (0, uuid_1.v4)(),
|
|
178
|
+
vaultId,
|
|
179
|
+
memberId: vault.metadata.ownerId,
|
|
180
|
+
action: brightchain_lib_1.AuditAction.VAULT_UPDATED,
|
|
181
|
+
metadata: { action: 'seed_regenerated' },
|
|
182
|
+
});
|
|
183
|
+
return newMnemonic;
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Extract an EntryPropertyRecord from a VaultEntry.
|
|
187
|
+
*/
|
|
188
|
+
entryToPropertyRecord(entry) {
|
|
189
|
+
return {
|
|
190
|
+
entryType: entry.type,
|
|
191
|
+
title: entry.title,
|
|
192
|
+
tags: entry.tags ?? [],
|
|
193
|
+
favorite: entry.favorite,
|
|
194
|
+
createdAt: entry.createdAt,
|
|
195
|
+
updatedAt: entry.updatedAt,
|
|
196
|
+
siteUrl: entry.type === 'login' ? entry.siteUrl : '',
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Check if block store operations are safe to perform.
|
|
201
|
+
* Block store operations require the global service provider to be initialized.
|
|
202
|
+
* Returns false if the service provider is not available.
|
|
203
|
+
*/
|
|
204
|
+
isBlockStoreOperationsSafe() {
|
|
205
|
+
try {
|
|
206
|
+
// Try to access the global service provider
|
|
207
|
+
// If it throws, block store operations are not safe
|
|
208
|
+
(0, brightchain_lib_1.getGlobalServiceProvider)();
|
|
209
|
+
return true;
|
|
210
|
+
}
|
|
211
|
+
catch {
|
|
212
|
+
return false;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Get a vault or throw VaultNotFoundError.
|
|
217
|
+
*/
|
|
218
|
+
getVaultOrThrow(vaultId) {
|
|
219
|
+
const vault = this.vaults.get(vaultId);
|
|
220
|
+
if (!vault) {
|
|
221
|
+
throw new VaultNotFoundError(vaultId);
|
|
222
|
+
}
|
|
223
|
+
return vault;
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Update the VCBL in the block store after entry changes.
|
|
227
|
+
* Creates a new VCBL with updated property records and entry addresses,
|
|
228
|
+
* encrypts it, and stores it in the block store.
|
|
229
|
+
* Requirements: 2.2, 2.3, 2.4, 3.2
|
|
230
|
+
*/
|
|
231
|
+
async updateVcblInBlockStore(vaultId, vault) {
|
|
232
|
+
if (!this.vcblService || !this.member) {
|
|
233
|
+
return; // VCBLService not available, skip block store update
|
|
234
|
+
}
|
|
235
|
+
if (!this.isBlockStoreOperationsSafe()) {
|
|
236
|
+
return; // Block store operations not safe, skip update
|
|
237
|
+
}
|
|
238
|
+
const indexEntry = this.vaultIndex.get(vaultId);
|
|
239
|
+
if (!indexEntry) {
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
// Build address list from block IDs (checksums)
|
|
243
|
+
const addressList = new Uint8Array(vault.blockIds.length * brightchain_lib_1.CONSTANTS['CHECKSUM'].SHA3_BUFFER_LENGTH);
|
|
244
|
+
for (let i = 0; i < vault.blockIds.length; i++) {
|
|
245
|
+
const checksum = brightchain_lib_1.Checksum.fromHex(vault.blockIds[i]);
|
|
246
|
+
addressList.set(checksum.toUint8Array(), i * brightchain_lib_1.CONSTANTS['CHECKSUM'].SHA3_BUFFER_LENGTH);
|
|
247
|
+
}
|
|
248
|
+
// Create new VCBL header with updated property records and addresses
|
|
249
|
+
const { headerData } = this.vcblService.makeVcblHeader(this.member, vault.metadata.name, [], // shared members - would need to convert string IDs to TID
|
|
250
|
+
vault.propertyRecords, addressList, this.blockStore.blockSize, brightchain_lib_1.BlockEncryptionType.None);
|
|
251
|
+
// Encrypt the VCBL header using the vault key
|
|
252
|
+
const encryptedVcbl = vaultEncryption_1.VaultEncryption.encrypt(vault.vaultKey, headerData);
|
|
253
|
+
// Calculate new checksum
|
|
254
|
+
const checksumService = new brightchain_lib_1.ChecksumService();
|
|
255
|
+
const newVcblChecksum = checksumService.calculateChecksum(encryptedVcbl);
|
|
256
|
+
const newVcblBlockId = newVcblChecksum.toHex();
|
|
257
|
+
// Remove old VCBL from block store if it exists
|
|
258
|
+
if (indexEntry.vcblBlockId) {
|
|
259
|
+
const hasOldVcbl = await this.blockStore.has(indexEntry.vcblBlockId);
|
|
260
|
+
if (hasOldVcbl) {
|
|
261
|
+
await this.blockStore.delete(indexEntry.vcblBlockId);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
// Store new VCBL in block store
|
|
265
|
+
await this.blockStore.put(newVcblBlockId, encryptedVcbl);
|
|
266
|
+
// Update index entry with new VCBL checksum
|
|
267
|
+
indexEntry.vcblChecksum = newVcblChecksum;
|
|
268
|
+
indexEntry.vcblBlockId = newVcblBlockId;
|
|
269
|
+
vault.metadata.vcblBlockId = newVcblBlockId;
|
|
270
|
+
}
|
|
271
|
+
// ─── Vault CRUD ───────────────────────────────────────────────
|
|
272
|
+
/**
|
|
273
|
+
* Create a new vault.
|
|
274
|
+
* Generates a vault-specific BIP39 mnemonic and derives the vault key from it.
|
|
275
|
+
* Uses bcrypt for secure password hashing.
|
|
276
|
+
*
|
|
277
|
+
* Security: Each vault has its own independent BIP39 seed that can be
|
|
278
|
+
* regenerated/cycled without affecting other vaults or the member's identity.
|
|
279
|
+
*
|
|
280
|
+
* Requirements: 1.1, 3.1, 3.2
|
|
281
|
+
*/
|
|
282
|
+
async createVault(memberId, name, masterPassword) {
|
|
283
|
+
// Check for duplicate vault name for this member
|
|
284
|
+
const memberVaultIds = this.memberVaults.get(memberId);
|
|
285
|
+
if (memberVaultIds) {
|
|
286
|
+
for (const vid of memberVaultIds) {
|
|
287
|
+
const existing = this.vaults.get(vid);
|
|
288
|
+
if (existing && existing.metadata.name === name) {
|
|
289
|
+
throw new VaultConflictError(name);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
const vaultId = (0, uuid_1.v4)();
|
|
294
|
+
// Generate vault-specific BIP39 mnemonic and seed (independent of member)
|
|
295
|
+
const { mnemonic: vaultMnemonic, seed: vaultSeed } = this.generateVaultBip39();
|
|
296
|
+
// Derive vault key from the vault's own BIP39 seed + master password
|
|
297
|
+
const vaultKey = brightchain_lib_1.VaultKeyDerivation.deriveVaultKey(vaultSeed, masterPassword, vaultId);
|
|
298
|
+
// Hash password using bcrypt (async, secure)
|
|
299
|
+
const masterPasswordHash = await this.hashMasterPasswordAsync(masterPassword);
|
|
300
|
+
const now = new Date();
|
|
301
|
+
// Create VCBL using VCBLService if available (Req 3.1, 3.2)
|
|
302
|
+
let vcblChecksum = null;
|
|
303
|
+
let vcblBlockId = (0, uuid_1.v4)(); // fallback simulated block ID
|
|
304
|
+
if (this.vcblService && this.member) {
|
|
305
|
+
// Create empty VCBL header with no entries
|
|
306
|
+
const emptyPropertyRecords = [];
|
|
307
|
+
const emptyAddressList = new Uint8Array(0);
|
|
308
|
+
const { headerData } = this.vcblService.makeVcblHeader(this.member, name, [], // no shared members initially
|
|
309
|
+
emptyPropertyRecords, emptyAddressList, this.blockStore.blockSize, brightchain_lib_1.BlockEncryptionType.None);
|
|
310
|
+
// Encrypt the VCBL header using the vault key
|
|
311
|
+
const encryptedVcbl = vaultEncryption_1.VaultEncryption.encrypt(vaultKey, headerData);
|
|
312
|
+
// Calculate checksum of the encrypted VCBL
|
|
313
|
+
const checksumService = new brightchain_lib_1.ChecksumService();
|
|
314
|
+
vcblChecksum = checksumService.calculateChecksum(encryptedVcbl);
|
|
315
|
+
// Store the encrypted VCBL in the block store
|
|
316
|
+
await this.blockStore.put(vcblChecksum.toHex(), encryptedVcbl);
|
|
317
|
+
vcblBlockId = vcblChecksum.toHex();
|
|
318
|
+
}
|
|
319
|
+
const metadata = {
|
|
320
|
+
id: vaultId,
|
|
321
|
+
name,
|
|
322
|
+
ownerId: memberId,
|
|
323
|
+
createdAt: now,
|
|
324
|
+
updatedAt: now,
|
|
325
|
+
entryCount: 0,
|
|
326
|
+
sharedWith: [],
|
|
327
|
+
vcblBlockId,
|
|
328
|
+
};
|
|
329
|
+
const auditLogger = new auditLogger_1.AuditLogger();
|
|
330
|
+
const stored = {
|
|
331
|
+
metadata,
|
|
332
|
+
vaultKey,
|
|
333
|
+
masterPasswordHash,
|
|
334
|
+
vaultMnemonic,
|
|
335
|
+
vaultSeed,
|
|
336
|
+
propertyRecords: [],
|
|
337
|
+
blockIds: [],
|
|
338
|
+
entries: new Map(),
|
|
339
|
+
auditLogger,
|
|
340
|
+
quorumThreshold: 0,
|
|
341
|
+
quorumApprovals: new Set(),
|
|
342
|
+
};
|
|
343
|
+
this.vaults.set(vaultId, stored);
|
|
344
|
+
// Populate lightweight vault index for quick lookups
|
|
345
|
+
const indexEntry = {
|
|
346
|
+
vcblChecksum, // Now set to actual checksum when VCBLService is available
|
|
347
|
+
ownerId: memberId,
|
|
348
|
+
name,
|
|
349
|
+
vaultKey,
|
|
350
|
+
masterPasswordHash,
|
|
351
|
+
vaultMnemonic,
|
|
352
|
+
vaultSeed,
|
|
353
|
+
sharedWith: [],
|
|
354
|
+
createdAt: now,
|
|
355
|
+
updatedAt: now,
|
|
356
|
+
entryCount: 0,
|
|
357
|
+
vcblBlockId,
|
|
358
|
+
};
|
|
359
|
+
this.vaultIndex.set(vaultId, indexEntry);
|
|
360
|
+
// Track member → vault mapping
|
|
361
|
+
if (!this.memberVaults.has(memberId)) {
|
|
362
|
+
this.memberVaults.set(memberId, new Set());
|
|
363
|
+
}
|
|
364
|
+
this.memberVaults.get(memberId).add(vaultId);
|
|
365
|
+
await auditLogger.log({
|
|
366
|
+
id: (0, uuid_1.v4)(),
|
|
367
|
+
vaultId,
|
|
368
|
+
memberId,
|
|
369
|
+
action: brightchain_lib_1.AuditAction.VAULT_CREATED,
|
|
370
|
+
});
|
|
371
|
+
return { ...metadata };
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* Open an existing vault with the correct master password.
|
|
375
|
+
* Returns vault metadata and property records (no entry decryption).
|
|
376
|
+
* When VCBLService is available, retrieves and parses VCBL from block store.
|
|
377
|
+
* Requirements: 1.2, 1.3, 3.3, 3.4
|
|
378
|
+
*/
|
|
379
|
+
async openVault(memberId, vaultId, masterPassword) {
|
|
380
|
+
const vault = this.vaults.get(vaultId);
|
|
381
|
+
if (!vault) {
|
|
382
|
+
// Don't reveal whether vault exists — return generic auth error
|
|
383
|
+
throw new VaultAuthenticationError();
|
|
384
|
+
}
|
|
385
|
+
// Verify ownership or shared access
|
|
386
|
+
if (vault.metadata.ownerId !== memberId &&
|
|
387
|
+
!vault.metadata.sharedWith.includes(memberId)) {
|
|
388
|
+
throw new VaultAuthenticationError();
|
|
389
|
+
}
|
|
390
|
+
// Verify master password using bcrypt (constant-time comparison)
|
|
391
|
+
const passwordValid = await this.verifyMasterPassword(masterPassword, vault.masterPasswordHash);
|
|
392
|
+
if (!passwordValid) {
|
|
393
|
+
throw new VaultAuthenticationError();
|
|
394
|
+
}
|
|
395
|
+
// Enforce quorum governance if configured
|
|
396
|
+
if (vault.quorumThreshold > 0 && !this.isQuorumMet(vaultId)) {
|
|
397
|
+
throw new VaultAuthenticationError();
|
|
398
|
+
}
|
|
399
|
+
await vault.auditLogger.log({
|
|
400
|
+
id: (0, uuid_1.v4)(),
|
|
401
|
+
vaultId,
|
|
402
|
+
memberId,
|
|
403
|
+
action: brightchain_lib_1.AuditAction.VAULT_OPENED,
|
|
404
|
+
});
|
|
405
|
+
// Try to retrieve and parse VCBL from block store (Req 3.3, 3.4)
|
|
406
|
+
const indexEntry = this.vaultIndex.get(vaultId);
|
|
407
|
+
if (this.vcblService && this.member && indexEntry?.vcblBlockId) {
|
|
408
|
+
const hasVcbl = await this.blockStore.has(indexEntry.vcblBlockId);
|
|
409
|
+
if (hasVcbl) {
|
|
410
|
+
// Retrieve encrypted VCBL from block store
|
|
411
|
+
const blockHandle = this.blockStore.get(indexEntry.vcblBlockId);
|
|
412
|
+
const encryptedVcbl = blockHandle.fullData;
|
|
413
|
+
// Decrypt VCBL using vault key
|
|
414
|
+
const decryptedVcbl = vaultEncryption_1.VaultEncryption.decrypt(vault.vaultKey, new Uint8Array(encryptedVcbl));
|
|
415
|
+
// Parse VCBL using VCBLBlock to extract property records and entry addresses
|
|
416
|
+
const vcblBlock = new brightchain_lib_1.VCBLBlock(decryptedVcbl, this.member, this.blockStore.blockSize, undefined, this.vcblService);
|
|
417
|
+
// Extract property records from VCBL (Req 3.3)
|
|
418
|
+
const propertyRecords = vcblBlock.propertyRecords;
|
|
419
|
+
// Extract entry addresses from VCBL (Req 3.4)
|
|
420
|
+
const entryAddresses = vcblBlock.addresses;
|
|
421
|
+
// Update in-memory vault with parsed data for consistency
|
|
422
|
+
vault.propertyRecords = [...propertyRecords];
|
|
423
|
+
vault.blockIds = entryAddresses.map((addr) => addr.toHex());
|
|
424
|
+
return {
|
|
425
|
+
metadata: { ...vault.metadata },
|
|
426
|
+
propertyRecords: [...propertyRecords],
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
// Fallback: return in-memory data when VCBLService is not available
|
|
431
|
+
return {
|
|
432
|
+
metadata: { ...vault.metadata },
|
|
433
|
+
propertyRecords: [...vault.propertyRecords],
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
/**
|
|
437
|
+
* List all vaults owned by or shared with a member.
|
|
438
|
+
* Returns metadata only — no decryption needed.
|
|
439
|
+
* Requirements: 1.4
|
|
440
|
+
*/
|
|
441
|
+
async listVaults(memberId) {
|
|
442
|
+
const result = [];
|
|
443
|
+
for (const vault of this.vaults.values()) {
|
|
444
|
+
if (vault.metadata.ownerId === memberId ||
|
|
445
|
+
vault.metadata.sharedWith.includes(memberId)) {
|
|
446
|
+
result.push({ ...vault.metadata });
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
return result;
|
|
450
|
+
}
|
|
451
|
+
/**
|
|
452
|
+
* Delete a vault and all its entries.
|
|
453
|
+
* Requirements: 1.5
|
|
454
|
+
*/
|
|
455
|
+
async deleteVault(memberId, vaultId, masterPassword) {
|
|
456
|
+
const vault = this.vaults.get(vaultId);
|
|
457
|
+
if (!vault) {
|
|
458
|
+
throw new VaultNotFoundError(vaultId);
|
|
459
|
+
}
|
|
460
|
+
if (vault.metadata.ownerId !== memberId) {
|
|
461
|
+
throw new VaultAuthenticationError();
|
|
462
|
+
}
|
|
463
|
+
// Verify master password using bcrypt (constant-time comparison)
|
|
464
|
+
const passwordValid = await this.verifyMasterPassword(masterPassword, vault.masterPasswordHash);
|
|
465
|
+
if (!passwordValid) {
|
|
466
|
+
throw new VaultAuthenticationError();
|
|
467
|
+
}
|
|
468
|
+
await vault.auditLogger.log({
|
|
469
|
+
id: (0, uuid_1.v4)(),
|
|
470
|
+
vaultId,
|
|
471
|
+
memberId,
|
|
472
|
+
action: brightchain_lib_1.AuditAction.VAULT_DELETED,
|
|
473
|
+
});
|
|
474
|
+
// Remove vault and all associated data
|
|
475
|
+
this.vaults.delete(vaultId);
|
|
476
|
+
this.vaultIndex.delete(vaultId);
|
|
477
|
+
const memberVaultIds = this.memberVaults.get(memberId);
|
|
478
|
+
if (memberVaultIds) {
|
|
479
|
+
memberVaultIds.delete(vaultId);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
// ─── Entry CRUD ─────────────────────────────────────────────
|
|
483
|
+
/**
|
|
484
|
+
* Add a new entry to a vault.
|
|
485
|
+
* Encrypts the entry using AES-256-GCM, stores in block store (when available),
|
|
486
|
+
* and appends block checksum + property record to VCBL parallel arrays.
|
|
487
|
+
* Requirements: 2.1, 2.2, 3.2
|
|
488
|
+
*/
|
|
489
|
+
async addEntry(vaultId, entry) {
|
|
490
|
+
const vault = this.getVaultOrThrow(vaultId);
|
|
491
|
+
// Assign ID and timestamps if not present
|
|
492
|
+
const now = new Date();
|
|
493
|
+
const fullEntry = {
|
|
494
|
+
...entry,
|
|
495
|
+
id: entry.id || (0, uuid_1.v4)(),
|
|
496
|
+
createdAt: entry.createdAt || now,
|
|
497
|
+
updatedAt: entry.updatedAt || now,
|
|
498
|
+
};
|
|
499
|
+
// Serialize and encrypt entry using AES-256-GCM (Req 2.2, 3.2)
|
|
500
|
+
const serialized = brightchain_lib_1.VaultSerializer.serializeEntry(fullEntry);
|
|
501
|
+
const encrypted = vaultEncryption_1.VaultEncryption.encryptString(vault.vaultKey, serialized);
|
|
502
|
+
// Calculate checksum for the entry block ID
|
|
503
|
+
const entryData = new Uint8Array(Buffer.from(encrypted, 'base64'));
|
|
504
|
+
const checksumService = new brightchain_lib_1.ChecksumService();
|
|
505
|
+
const entryChecksum = checksumService.calculateChecksum(entryData);
|
|
506
|
+
const entryBlockId = entryChecksum.toHex();
|
|
507
|
+
// Store encrypted entry in block store if operations are safe (Req 2.2)
|
|
508
|
+
if (this.isBlockStoreOperationsSafe()) {
|
|
509
|
+
await this.blockStore.put(entryBlockId, entryData);
|
|
510
|
+
}
|
|
511
|
+
// Also keep in-memory for backward compatibility
|
|
512
|
+
vault.entries.set(fullEntry.id, encrypted);
|
|
513
|
+
vault.propertyRecords.push(this.entryToPropertyRecord(fullEntry));
|
|
514
|
+
vault.blockIds.push(entryBlockId);
|
|
515
|
+
vault.metadata.entryCount = vault.propertyRecords.length;
|
|
516
|
+
vault.metadata.updatedAt = now;
|
|
517
|
+
// Update vault index entry count
|
|
518
|
+
const indexEntry = this.vaultIndex.get(vaultId);
|
|
519
|
+
if (indexEntry) {
|
|
520
|
+
indexEntry.entryCount = vault.metadata.entryCount;
|
|
521
|
+
indexEntry.updatedAt = now;
|
|
522
|
+
}
|
|
523
|
+
// Update VCBL in block store if VCBLService is available (Req 2.2)
|
|
524
|
+
await this.updateVcblInBlockStore(vaultId, vault);
|
|
525
|
+
await vault.auditLogger.log({
|
|
526
|
+
id: (0, uuid_1.v4)(),
|
|
527
|
+
vaultId,
|
|
528
|
+
memberId: vault.metadata.ownerId,
|
|
529
|
+
action: brightchain_lib_1.AuditAction.ENTRY_CREATED,
|
|
530
|
+
metadata: { entryId: fullEntry.id, entryType: fullEntry.type },
|
|
531
|
+
});
|
|
532
|
+
return fullEntry;
|
|
533
|
+
}
|
|
534
|
+
/**
|
|
535
|
+
* Get a single entry by ID from a vault.
|
|
536
|
+
* Retrieves from block store and decrypts the entry using AES-256-GCM.
|
|
537
|
+
* Requirements: 2.2, 2.3, 3.2
|
|
538
|
+
*/
|
|
539
|
+
async getEntry(vaultId, entryId) {
|
|
540
|
+
const vault = this.getVaultOrThrow(vaultId);
|
|
541
|
+
// First try to get from in-memory cache (backward compatibility)
|
|
542
|
+
let encrypted = vault.entries.get(entryId);
|
|
543
|
+
// If not in memory, try to find in block store using blockIds
|
|
544
|
+
if (!encrypted) {
|
|
545
|
+
// Find the block ID for this entry by looking up the entry ID mapping
|
|
546
|
+
// The blockIds array contains checksums, we need to find the right one
|
|
547
|
+
// For now, fall back to in-memory only since we store by checksum not entry ID
|
|
548
|
+
throw new EntryNotFoundError(entryId);
|
|
549
|
+
}
|
|
550
|
+
await vault.auditLogger.log({
|
|
551
|
+
id: (0, uuid_1.v4)(),
|
|
552
|
+
vaultId,
|
|
553
|
+
memberId: vault.metadata.ownerId,
|
|
554
|
+
action: brightchain_lib_1.AuditAction.ENTRY_READ,
|
|
555
|
+
metadata: { entryId },
|
|
556
|
+
});
|
|
557
|
+
// Decrypt and deserialize entry (Req 2.3, 3.2)
|
|
558
|
+
const decrypted = vaultEncryption_1.VaultEncryption.decryptString(vault.vaultKey, encrypted);
|
|
559
|
+
return brightchain_lib_1.VaultSerializer.deserializeEntry(decrypted);
|
|
560
|
+
}
|
|
561
|
+
/**
|
|
562
|
+
* Update an existing entry in a vault.
|
|
563
|
+
* Decrypts, updates, re-encrypts using AES-256-GCM, stores in block store (when available),
|
|
564
|
+
* and updates the VCBL property record.
|
|
565
|
+
* Requirements: 2.3, 2.4, 3.2
|
|
566
|
+
*/
|
|
567
|
+
async updateEntry(vaultId, entryId, updates) {
|
|
568
|
+
const vault = this.getVaultOrThrow(vaultId);
|
|
569
|
+
const encrypted = vault.entries.get(entryId);
|
|
570
|
+
if (!encrypted) {
|
|
571
|
+
throw new EntryNotFoundError(entryId);
|
|
572
|
+
}
|
|
573
|
+
// Decrypt existing entry (Req 2.3, 3.2)
|
|
574
|
+
const decrypted = vaultEncryption_1.VaultEncryption.decryptString(vault.vaultKey, encrypted);
|
|
575
|
+
const existing = brightchain_lib_1.VaultSerializer.deserializeEntry(decrypted);
|
|
576
|
+
const now = new Date();
|
|
577
|
+
const updated = {
|
|
578
|
+
...existing,
|
|
579
|
+
...updates,
|
|
580
|
+
id: entryId, // preserve original ID
|
|
581
|
+
type: existing.type, // preserve original type
|
|
582
|
+
createdAt: existing.createdAt, // preserve creation date
|
|
583
|
+
updatedAt: now,
|
|
584
|
+
};
|
|
585
|
+
// Re-serialize and re-encrypt (Req 2.4, 3.2)
|
|
586
|
+
const newSerialized = brightchain_lib_1.VaultSerializer.serializeEntry(updated);
|
|
587
|
+
const newEncrypted = vaultEncryption_1.VaultEncryption.encryptString(vault.vaultKey, newSerialized);
|
|
588
|
+
// Find the index of this entry in the entries map
|
|
589
|
+
const entryKeys = Array.from(vault.entries.keys());
|
|
590
|
+
const index = entryKeys.indexOf(entryId);
|
|
591
|
+
// Calculate new checksum and store in block store if safe
|
|
592
|
+
const entryData = new Uint8Array(Buffer.from(newEncrypted, 'base64'));
|
|
593
|
+
const checksumService = new brightchain_lib_1.ChecksumService();
|
|
594
|
+
const newChecksum = checksumService.calculateChecksum(entryData);
|
|
595
|
+
const newBlockId = newChecksum.toHex();
|
|
596
|
+
if (this.isBlockStoreOperationsSafe()) {
|
|
597
|
+
// Remove old block from store if it exists
|
|
598
|
+
if (index !== -1 &&
|
|
599
|
+
index < vault.blockIds.length &&
|
|
600
|
+
vault.blockIds[index]) {
|
|
601
|
+
const oldBlockId = vault.blockIds[index];
|
|
602
|
+
const hasOldBlock = await this.blockStore.has(oldBlockId);
|
|
603
|
+
if (hasOldBlock) {
|
|
604
|
+
await this.blockStore.delete(oldBlockId);
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
// Store new encrypted entry in block store
|
|
608
|
+
await this.blockStore.put(newBlockId, entryData);
|
|
609
|
+
}
|
|
610
|
+
// Update in-memory cache
|
|
611
|
+
vault.entries.set(entryId, newEncrypted);
|
|
612
|
+
// Update the parallel property record and block ID at the matching index
|
|
613
|
+
if (index !== -1 && index < vault.propertyRecords.length) {
|
|
614
|
+
vault.propertyRecords[index] = this.entryToPropertyRecord(updated);
|
|
615
|
+
vault.blockIds[index] = newBlockId;
|
|
616
|
+
}
|
|
617
|
+
vault.metadata.updatedAt = now;
|
|
618
|
+
// Update VCBL in block store (Req 2.4)
|
|
619
|
+
await this.updateVcblInBlockStore(vaultId, vault);
|
|
620
|
+
await vault.auditLogger.log({
|
|
621
|
+
id: (0, uuid_1.v4)(),
|
|
622
|
+
vaultId,
|
|
623
|
+
memberId: vault.metadata.ownerId,
|
|
624
|
+
action: brightchain_lib_1.AuditAction.ENTRY_UPDATED,
|
|
625
|
+
metadata: { entryId },
|
|
626
|
+
});
|
|
627
|
+
return updated;
|
|
628
|
+
}
|
|
629
|
+
/**
|
|
630
|
+
* Delete an entry from a vault.
|
|
631
|
+
* Removes the entry block from block store (when available), and removes from VCBL at the matching index.
|
|
632
|
+
* Requirements: 2.4
|
|
633
|
+
*/
|
|
634
|
+
async deleteEntry(vaultId, entryId) {
|
|
635
|
+
const vault = this.getVaultOrThrow(vaultId);
|
|
636
|
+
if (!vault.entries.has(entryId)) {
|
|
637
|
+
throw new EntryNotFoundError(entryId);
|
|
638
|
+
}
|
|
639
|
+
// Find the index of this entry in the entries map
|
|
640
|
+
const entryKeys = Array.from(vault.entries.keys());
|
|
641
|
+
const index = entryKeys.indexOf(entryId);
|
|
642
|
+
// Remove from block store if we have a valid block ID and operations are safe
|
|
643
|
+
if (index !== -1 && index < vault.blockIds.length) {
|
|
644
|
+
const blockId = vault.blockIds[index];
|
|
645
|
+
if (this.isBlockStoreOperationsSafe()) {
|
|
646
|
+
const hasBlock = await this.blockStore.has(blockId);
|
|
647
|
+
if (hasBlock) {
|
|
648
|
+
await this.blockStore.delete(blockId);
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
// Remove from parallel arrays at matching index
|
|
652
|
+
vault.propertyRecords.splice(index, 1);
|
|
653
|
+
vault.blockIds.splice(index, 1);
|
|
654
|
+
}
|
|
655
|
+
vault.entries.delete(entryId);
|
|
656
|
+
vault.metadata.entryCount = vault.propertyRecords.length;
|
|
657
|
+
vault.metadata.updatedAt = new Date();
|
|
658
|
+
// Update vault index entry count
|
|
659
|
+
const indexEntry = this.vaultIndex.get(vaultId);
|
|
660
|
+
if (indexEntry) {
|
|
661
|
+
indexEntry.entryCount = vault.metadata.entryCount;
|
|
662
|
+
indexEntry.updatedAt = vault.metadata.updatedAt;
|
|
663
|
+
}
|
|
664
|
+
// Update VCBL in block store (Req 2.4)
|
|
665
|
+
await this.updateVcblInBlockStore(vaultId, vault);
|
|
666
|
+
await vault.auditLogger.log({
|
|
667
|
+
id: (0, uuid_1.v4)(),
|
|
668
|
+
vaultId,
|
|
669
|
+
memberId: vault.metadata.ownerId,
|
|
670
|
+
action: brightchain_lib_1.AuditAction.ENTRY_DELETED,
|
|
671
|
+
metadata: { entryId },
|
|
672
|
+
});
|
|
673
|
+
}
|
|
674
|
+
/**
|
|
675
|
+
* Search entries in a vault by filtering VCBL property records.
|
|
676
|
+
* Requirements: 2.9
|
|
677
|
+
*/
|
|
678
|
+
async searchEntries(vaultId, query) {
|
|
679
|
+
const vault = this.getVaultOrThrow(vaultId);
|
|
680
|
+
return vault.propertyRecords.filter((record) => {
|
|
681
|
+
// Text search: match against title and siteUrl
|
|
682
|
+
if (query.text) {
|
|
683
|
+
const lowerText = query.text.toLowerCase();
|
|
684
|
+
const titleMatch = record.title.toLowerCase().includes(lowerText);
|
|
685
|
+
const urlMatch = record.siteUrl.toLowerCase().includes(lowerText);
|
|
686
|
+
if (!titleMatch && !urlMatch) {
|
|
687
|
+
return false;
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
// Type filter
|
|
691
|
+
if (query.type && record.entryType !== query.type) {
|
|
692
|
+
return false;
|
|
693
|
+
}
|
|
694
|
+
// Tag filter: any match
|
|
695
|
+
if (query.tags && query.tags.length > 0) {
|
|
696
|
+
const hasMatchingTag = query.tags.some((tag) => record.tags.includes(tag));
|
|
697
|
+
if (!hasMatchingTag) {
|
|
698
|
+
return false;
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
// Favorite filter
|
|
702
|
+
if (query.favorite !== undefined && record.favorite !== query.favorite) {
|
|
703
|
+
return false;
|
|
704
|
+
}
|
|
705
|
+
return true;
|
|
706
|
+
});
|
|
707
|
+
}
|
|
708
|
+
// ─── Sharing ────────────────────────────────────────────────
|
|
709
|
+
/**
|
|
710
|
+
* Share a vault with one or more recipients.
|
|
711
|
+
* Updates the VCBL header shared list and tracks member→vault mapping.
|
|
712
|
+
* Requirements: 4.1, 4.2
|
|
713
|
+
*/
|
|
714
|
+
async shareVault(vaultId, recipientMemberIds) {
|
|
715
|
+
const vault = this.getVaultOrThrow(vaultId);
|
|
716
|
+
const indexEntry = this.vaultIndex.get(vaultId);
|
|
717
|
+
for (const recipientId of recipientMemberIds) {
|
|
718
|
+
if (!vault.metadata.sharedWith.includes(recipientId)) {
|
|
719
|
+
vault.metadata.sharedWith.push(recipientId);
|
|
720
|
+
// Update vault index
|
|
721
|
+
if (indexEntry) {
|
|
722
|
+
indexEntry.sharedWith.push(recipientId);
|
|
723
|
+
}
|
|
724
|
+
// Track in memberVaults for listing
|
|
725
|
+
if (!this.memberVaults.has(recipientId)) {
|
|
726
|
+
this.memberVaults.set(recipientId, new Set());
|
|
727
|
+
}
|
|
728
|
+
this.memberVaults.get(recipientId).add(vaultId);
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
vault.metadata.updatedAt = new Date();
|
|
732
|
+
if (indexEntry) {
|
|
733
|
+
indexEntry.updatedAt = vault.metadata.updatedAt;
|
|
734
|
+
}
|
|
735
|
+
await vault.auditLogger.log({
|
|
736
|
+
id: (0, uuid_1.v4)(),
|
|
737
|
+
vaultId,
|
|
738
|
+
memberId: vault.metadata.ownerId,
|
|
739
|
+
action: brightchain_lib_1.AuditAction.VAULT_SHARED,
|
|
740
|
+
metadata: { recipients: recipientMemberIds.join(',') },
|
|
741
|
+
});
|
|
742
|
+
}
|
|
743
|
+
/**
|
|
744
|
+
* Revoke a member's access to a shared vault.
|
|
745
|
+
* Removes from VCBL header shared list and re-keys the vault.
|
|
746
|
+
* Requirements: 4.3
|
|
747
|
+
*/
|
|
748
|
+
async revokeShare(vaultId, memberId) {
|
|
749
|
+
const vault = this.getVaultOrThrow(vaultId);
|
|
750
|
+
const indexEntry = this.vaultIndex.get(vaultId);
|
|
751
|
+
const idx = vault.metadata.sharedWith.indexOf(memberId);
|
|
752
|
+
if (idx === -1) {
|
|
753
|
+
return; // Not shared with this member, no-op
|
|
754
|
+
}
|
|
755
|
+
vault.metadata.sharedWith.splice(idx, 1);
|
|
756
|
+
// Update vault index
|
|
757
|
+
if (indexEntry) {
|
|
758
|
+
const indexIdx = indexEntry.sharedWith.indexOf(memberId);
|
|
759
|
+
if (indexIdx !== -1) {
|
|
760
|
+
indexEntry.sharedWith.splice(indexIdx, 1);
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
// Remove from memberVaults tracking
|
|
764
|
+
const memberVaultIds = this.memberVaults.get(memberId);
|
|
765
|
+
if (memberVaultIds) {
|
|
766
|
+
memberVaultIds.delete(vaultId);
|
|
767
|
+
}
|
|
768
|
+
// Re-key the vault by regenerating the BIP39 seed
|
|
769
|
+
// This ensures the revoked member's old key material is completely useless
|
|
770
|
+
const { mnemonic: newMnemonic, seed: newSeed } = this.generateVaultBip39();
|
|
771
|
+
// We need the master password to derive the new key, but we don't have it here.
|
|
772
|
+
// For now, we'll mark the vault as needing re-keying on next open.
|
|
773
|
+
// In a production system, this would trigger a re-keying flow.
|
|
774
|
+
//
|
|
775
|
+
// Alternative: Store the vault key encrypted with the owner's public key,
|
|
776
|
+
// allowing re-keying without the master password.
|
|
777
|
+
//
|
|
778
|
+
// For now, we update the seed and invalidate emergency shares.
|
|
779
|
+
// The vault key will be re-derived on next successful open.
|
|
780
|
+
vault.vaultMnemonic = newMnemonic;
|
|
781
|
+
vault.vaultSeed = newSeed;
|
|
782
|
+
vault.metadata.updatedAt = new Date();
|
|
783
|
+
// Invalidate any existing emergency shares (they used the old key)
|
|
784
|
+
if (vault.emergencyConfig) {
|
|
785
|
+
vault.emergencyConfig = undefined;
|
|
786
|
+
vault.emergencyShares = undefined;
|
|
787
|
+
}
|
|
788
|
+
// Update vault index
|
|
789
|
+
if (indexEntry) {
|
|
790
|
+
indexEntry.vaultMnemonic = newMnemonic;
|
|
791
|
+
indexEntry.vaultSeed = newSeed;
|
|
792
|
+
indexEntry.updatedAt = vault.metadata.updatedAt;
|
|
793
|
+
}
|
|
794
|
+
await vault.auditLogger.log({
|
|
795
|
+
id: (0, uuid_1.v4)(),
|
|
796
|
+
vaultId,
|
|
797
|
+
memberId: vault.metadata.ownerId,
|
|
798
|
+
action: brightchain_lib_1.AuditAction.VAULT_SHARE_REVOKED,
|
|
799
|
+
metadata: { revokedMemberId: memberId, seedRegenerated: 'true' },
|
|
800
|
+
});
|
|
801
|
+
}
|
|
802
|
+
// ─── Quorum Governance ─────────────────────────────────────────
|
|
803
|
+
/**
|
|
804
|
+
* Configure quorum governance for a shared vault.
|
|
805
|
+
* When threshold > 0, opening the vault requires that many member approvals.
|
|
806
|
+
* Requirements: 4.4
|
|
807
|
+
*/
|
|
808
|
+
async configureQuorumGovernance(vaultId, threshold) {
|
|
809
|
+
const vault = this.getVaultOrThrow(vaultId);
|
|
810
|
+
if (threshold < 0) {
|
|
811
|
+
throw new EmergencyAccessError('Quorum threshold must be non-negative');
|
|
812
|
+
}
|
|
813
|
+
const totalMembers = vault.metadata.sharedWith.length + 1; // +1 for owner
|
|
814
|
+
if (threshold > totalMembers) {
|
|
815
|
+
throw new EmergencyAccessError(`Quorum threshold (${threshold}) cannot exceed total members (${totalMembers})`);
|
|
816
|
+
}
|
|
817
|
+
vault.quorumThreshold = threshold;
|
|
818
|
+
vault.quorumApprovals = new Set();
|
|
819
|
+
vault.metadata.updatedAt = new Date();
|
|
820
|
+
await vault.auditLogger.log({
|
|
821
|
+
id: (0, uuid_1.v4)(),
|
|
822
|
+
vaultId,
|
|
823
|
+
memberId: vault.metadata.ownerId,
|
|
824
|
+
action: brightchain_lib_1.AuditAction.QUORUM_CONFIGURED,
|
|
825
|
+
metadata: { quorumThreshold: String(threshold) },
|
|
826
|
+
});
|
|
827
|
+
}
|
|
828
|
+
/**
|
|
829
|
+
* Submit a quorum approval for a vault.
|
|
830
|
+
* The member must be the owner or a shared member.
|
|
831
|
+
* Returns true if the quorum threshold has been met.
|
|
832
|
+
* Requirements: 4.4
|
|
833
|
+
*/
|
|
834
|
+
approveQuorumAccess(vaultId, memberId) {
|
|
835
|
+
const vault = this.getVaultOrThrow(vaultId);
|
|
836
|
+
if (vault.metadata.ownerId !== memberId &&
|
|
837
|
+
!vault.metadata.sharedWith.includes(memberId)) {
|
|
838
|
+
throw new VaultAuthenticationError();
|
|
839
|
+
}
|
|
840
|
+
vault.quorumApprovals.add(memberId);
|
|
841
|
+
return vault.quorumApprovals.size >= vault.quorumThreshold;
|
|
842
|
+
}
|
|
843
|
+
/**
|
|
844
|
+
* Check whether the quorum threshold has been met for a vault.
|
|
845
|
+
*/
|
|
846
|
+
isQuorumMet(vaultId) {
|
|
847
|
+
const vault = this.getVaultOrThrow(vaultId);
|
|
848
|
+
if (vault.quorumThreshold === 0)
|
|
849
|
+
return true;
|
|
850
|
+
return vault.quorumApprovals.size >= vault.quorumThreshold;
|
|
851
|
+
}
|
|
852
|
+
/**
|
|
853
|
+
* Reset quorum approvals (e.g. after vault is closed or session ends).
|
|
854
|
+
*/
|
|
855
|
+
resetQuorumApprovals(vaultId) {
|
|
856
|
+
const vault = this.getVaultOrThrow(vaultId);
|
|
857
|
+
vault.quorumApprovals = new Set();
|
|
858
|
+
}
|
|
859
|
+
// ─── Master Password ─────────────────────────────────────────
|
|
860
|
+
/**
|
|
861
|
+
* Change the master password for a vault.
|
|
862
|
+
* Verifies old password, re-derives vault key with new password,
|
|
863
|
+
* decrypts all entries with old key, and re-encrypts with new key.
|
|
864
|
+
*
|
|
865
|
+
* Note: This only changes the password, not the vault's BIP39 seed.
|
|
866
|
+
* Use regenerateVaultSeed() for full key rotation.
|
|
867
|
+
*
|
|
868
|
+
* Requirements: 3.4, 3.2
|
|
869
|
+
*/
|
|
870
|
+
async changeMasterPassword(memberId, vaultId, oldPassword, newPassword) {
|
|
871
|
+
const vault = this.getVaultOrThrow(vaultId);
|
|
872
|
+
// Verify ownership
|
|
873
|
+
if (vault.metadata.ownerId !== memberId) {
|
|
874
|
+
throw new VaultAuthenticationError();
|
|
875
|
+
}
|
|
876
|
+
// Verify old password using bcrypt (constant-time comparison)
|
|
877
|
+
const oldPasswordValid = await this.verifyMasterPassword(oldPassword, vault.masterPasswordHash);
|
|
878
|
+
if (!oldPasswordValid) {
|
|
879
|
+
throw new VaultAuthenticationError();
|
|
880
|
+
}
|
|
881
|
+
// Re-derive vault key with new password (using vault's own BIP39 seed)
|
|
882
|
+
const newVaultKey = brightchain_lib_1.VaultKeyDerivation.deriveVaultKey(vault.vaultSeed, newPassword, vaultId);
|
|
883
|
+
// Re-encrypt all entries with new key (Req 3.4, 3.2)
|
|
884
|
+
const oldVaultKey = vault.vaultKey;
|
|
885
|
+
for (const [entryId, encryptedEntry] of vault.entries) {
|
|
886
|
+
// Decrypt with old key
|
|
887
|
+
const decrypted = vaultEncryption_1.VaultEncryption.decryptString(oldVaultKey, encryptedEntry);
|
|
888
|
+
// Re-encrypt with new key
|
|
889
|
+
const reEncrypted = vaultEncryption_1.VaultEncryption.encryptString(newVaultKey, decrypted);
|
|
890
|
+
vault.entries.set(entryId, reEncrypted);
|
|
891
|
+
}
|
|
892
|
+
vault.vaultKey = newVaultKey;
|
|
893
|
+
// Hash new password using bcrypt
|
|
894
|
+
vault.masterPasswordHash = await this.hashMasterPasswordAsync(newPassword);
|
|
895
|
+
vault.metadata.updatedAt = new Date();
|
|
896
|
+
}
|
|
897
|
+
// ─── Emergency Access ───────────────────────────────────────
|
|
898
|
+
/**
|
|
899
|
+
* Split the vault key into Shamir shares for emergency recovery.
|
|
900
|
+
* Uses proper Shamir Secret Sharing (polynomial interpolation over GF(256)).
|
|
901
|
+
* Each trustee receives an encrypted share; threshold shares are needed to reconstruct.
|
|
902
|
+
*
|
|
903
|
+
* Security: Uses @digitaldefiance/secrets library which implements proper
|
|
904
|
+
* Shamir Secret Sharing with cryptographically secure random coefficients.
|
|
905
|
+
*
|
|
906
|
+
* Requirements: 10.1, 10.2, 10.3
|
|
907
|
+
*/
|
|
908
|
+
async configureEmergencyAccess(vaultId, threshold, trustees) {
|
|
909
|
+
const vault = this.getVaultOrThrow(vaultId);
|
|
910
|
+
const totalShares = trustees.length;
|
|
911
|
+
if (threshold < 2) {
|
|
912
|
+
throw new EmergencyAccessError('Threshold must be at least 2 for security');
|
|
913
|
+
}
|
|
914
|
+
if (threshold > totalShares) {
|
|
915
|
+
throw new EmergencyAccessError(`Threshold (${threshold}) cannot exceed total shares (${totalShares})`);
|
|
916
|
+
}
|
|
917
|
+
// Initialize secrets library with correct bit size for the number of shares
|
|
918
|
+
// Must have at least 3 bits (min 8 shares capacity)
|
|
919
|
+
const bits = Math.max(3, Math.ceil(Math.log2(totalShares + 1)));
|
|
920
|
+
secrets.init(bits, 'nodeCryptoRandomBytes');
|
|
921
|
+
// Convert vault key to hex string for Shamir splitting
|
|
922
|
+
const keyHex = Buffer.from(vault.vaultKey).toString('hex');
|
|
923
|
+
// Use real Shamir Secret Sharing to split the vault key
|
|
924
|
+
// share(secret, totalShares, threshold) returns array of hex share strings
|
|
925
|
+
// These hex strings include share metadata (index prefix) and must be preserved as-is
|
|
926
|
+
const shamirShares = secrets.share(keyHex, totalShares, threshold);
|
|
927
|
+
// Create encrypted shares for each trustee
|
|
928
|
+
// Store shares as hex strings encoded to UTF-8 bytes to preserve the format
|
|
929
|
+
const shares = shamirShares.map((shareHex, index) => {
|
|
930
|
+
// Store the hex string as UTF-8 bytes to preserve the share format exactly
|
|
931
|
+
const shareData = new Uint8Array(Buffer.from(shareHex, 'utf8'));
|
|
932
|
+
return {
|
|
933
|
+
trusteeId: trustees[index],
|
|
934
|
+
encryptedShareData: shareData,
|
|
935
|
+
};
|
|
936
|
+
});
|
|
937
|
+
const config = {
|
|
938
|
+
vaultId,
|
|
939
|
+
threshold,
|
|
940
|
+
totalShares,
|
|
941
|
+
trustees: [...trustees],
|
|
942
|
+
};
|
|
943
|
+
vault.emergencyConfig = config;
|
|
944
|
+
vault.emergencyShares = shares;
|
|
945
|
+
await vault.auditLogger.log({
|
|
946
|
+
id: (0, uuid_1.v4)(),
|
|
947
|
+
vaultId,
|
|
948
|
+
memberId: vault.metadata.ownerId,
|
|
949
|
+
action: brightchain_lib_1.AuditAction.EMERGENCY_CONFIGURED,
|
|
950
|
+
metadata: { threshold: String(threshold), trustees: trustees.join(',') },
|
|
951
|
+
});
|
|
952
|
+
return { ...config };
|
|
953
|
+
}
|
|
954
|
+
/**
|
|
955
|
+
* Recover a vault using emergency shares.
|
|
956
|
+
* Uses proper Shamir Secret Sharing reconstruction (polynomial interpolation).
|
|
957
|
+
* Requires at least threshold shares to reconstruct the vault key.
|
|
958
|
+
*
|
|
959
|
+
* Security: The combine() function performs Lagrange interpolation over GF(256)
|
|
960
|
+
* to reconstruct the secret. With fewer than threshold shares, reconstruction
|
|
961
|
+
* is cryptographically impossible.
|
|
962
|
+
*
|
|
963
|
+
* Requirements: 10.2, 10.3, 10.4
|
|
964
|
+
*/
|
|
965
|
+
async recoverWithShares(vaultId, shares) {
|
|
966
|
+
const vault = this.getVaultOrThrow(vaultId);
|
|
967
|
+
if (!vault.emergencyConfig || !vault.emergencyShares) {
|
|
968
|
+
throw new EmergencyAccessError('Emergency access not configured');
|
|
969
|
+
}
|
|
970
|
+
const { threshold, totalShares } = vault.emergencyConfig;
|
|
971
|
+
if (shares.length < threshold) {
|
|
972
|
+
throw new EmergencyAccessError(`Insufficient shares: need ${threshold}, got ${shares.length}`);
|
|
973
|
+
}
|
|
974
|
+
// Verify all provided shares are from valid trustees
|
|
975
|
+
for (const share of shares) {
|
|
976
|
+
const storedShare = vault.emergencyShares.find((s) => s.trusteeId === share.trusteeId);
|
|
977
|
+
if (!storedShare) {
|
|
978
|
+
throw new EmergencyAccessError(`Invalid share from trustee: ${share.trusteeId}`);
|
|
979
|
+
}
|
|
980
|
+
// Verify share data matches using constant-time comparison
|
|
981
|
+
if (share.encryptedShareData.length !==
|
|
982
|
+
storedShare.encryptedShareData.length) {
|
|
983
|
+
throw new EmergencyAccessError(`Invalid share data from trustee: ${share.trusteeId}`);
|
|
984
|
+
}
|
|
985
|
+
const shareBuffer = Buffer.from(share.encryptedShareData);
|
|
986
|
+
const storedBuffer = Buffer.from(storedShare.encryptedShareData);
|
|
987
|
+
if (!crypto.timingSafeEqual(shareBuffer, storedBuffer)) {
|
|
988
|
+
throw new EmergencyAccessError(`Invalid share data from trustee: ${share.trusteeId}`);
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
// Initialize secrets library with the same bit size used during share creation
|
|
992
|
+
const bits = Math.max(3, Math.ceil(Math.log2(totalShares + 1)));
|
|
993
|
+
secrets.init(bits, 'nodeCryptoRandomBytes');
|
|
994
|
+
// Convert shares back to hex strings (they were stored as UTF-8 encoded hex)
|
|
995
|
+
const shareHexStrings = shares.map((s) => Buffer.from(s.encryptedShareData).toString('utf8'));
|
|
996
|
+
// Reconstruct the vault key using Shamir Secret Sharing
|
|
997
|
+
const reconstructedKeyHex = secrets.combine(shareHexStrings);
|
|
998
|
+
const reconstructedKey = new Uint8Array(Buffer.from(reconstructedKeyHex, 'hex'));
|
|
999
|
+
// Verify the reconstructed key matches the vault key
|
|
1000
|
+
// This is a sanity check - in production, the vault key wouldn't be stored
|
|
1001
|
+
if (reconstructedKey.length !== vault.vaultKey.length) {
|
|
1002
|
+
throw new EmergencyAccessError('Share reconstruction failed - key length mismatch');
|
|
1003
|
+
}
|
|
1004
|
+
if (!crypto.timingSafeEqual(Buffer.from(reconstructedKey), Buffer.from(vault.vaultKey))) {
|
|
1005
|
+
throw new EmergencyAccessError('Share reconstruction failed - invalid shares');
|
|
1006
|
+
}
|
|
1007
|
+
await vault.auditLogger.log({
|
|
1008
|
+
id: (0, uuid_1.v4)(),
|
|
1009
|
+
vaultId,
|
|
1010
|
+
memberId: shares[0].trusteeId,
|
|
1011
|
+
action: brightchain_lib_1.AuditAction.EMERGENCY_RECOVERED,
|
|
1012
|
+
metadata: {
|
|
1013
|
+
sharesUsed: String(shares.length),
|
|
1014
|
+
trustees: shares.map((s) => s.trusteeId).join(','),
|
|
1015
|
+
},
|
|
1016
|
+
});
|
|
1017
|
+
return {
|
|
1018
|
+
metadata: { ...vault.metadata },
|
|
1019
|
+
propertyRecords: [...vault.propertyRecords],
|
|
1020
|
+
};
|
|
1021
|
+
}
|
|
1022
|
+
/**
|
|
1023
|
+
* Revoke emergency access configuration, invalidating all existing shares.
|
|
1024
|
+
* Requirements: 10.5
|
|
1025
|
+
*/
|
|
1026
|
+
async revokeEmergencyAccess(vaultId) {
|
|
1027
|
+
const vault = this.getVaultOrThrow(vaultId);
|
|
1028
|
+
vault.emergencyConfig = undefined;
|
|
1029
|
+
vault.emergencyShares = undefined;
|
|
1030
|
+
}
|
|
1031
|
+
/**
|
|
1032
|
+
* Get the encrypted shares for a vault's emergency access.
|
|
1033
|
+
* In production, shares would be distributed to trustees via secure channels.
|
|
1034
|
+
* Requirements: 10.2
|
|
1035
|
+
*/
|
|
1036
|
+
getEmergencyShares(vaultId) {
|
|
1037
|
+
const vault = this.getVaultOrThrow(vaultId);
|
|
1038
|
+
if (!vault.emergencyShares) {
|
|
1039
|
+
throw new EmergencyAccessError('Emergency access not configured');
|
|
1040
|
+
}
|
|
1041
|
+
return vault.emergencyShares.map((s) => ({
|
|
1042
|
+
trusteeId: s.trusteeId,
|
|
1043
|
+
encryptedShareData: new Uint8Array(s.encryptedShareData),
|
|
1044
|
+
}));
|
|
1045
|
+
}
|
|
1046
|
+
// ─── Autofill ──────────────────────────────────────────────────
|
|
1047
|
+
/**
|
|
1048
|
+
* Get autofill payload for a given site URL.
|
|
1049
|
+
* Searches VCBL property records by siteUrl, decrypts matching login entries,
|
|
1050
|
+
* and includes TOTP code if configured.
|
|
1051
|
+
* Requirements: 5.7
|
|
1052
|
+
*/
|
|
1053
|
+
async getAutofillPayload(vaultId, siteUrl) {
|
|
1054
|
+
const vault = this.getVaultOrThrow(vaultId);
|
|
1055
|
+
const lowerUrl = siteUrl.toLowerCase();
|
|
1056
|
+
// Find matching login property records by siteUrl
|
|
1057
|
+
const matchingEntryIds = [];
|
|
1058
|
+
for (let i = 0; i < vault.propertyRecords.length; i++) {
|
|
1059
|
+
const record = vault.propertyRecords[i];
|
|
1060
|
+
if (record.entryType === 'login' &&
|
|
1061
|
+
record.siteUrl.toLowerCase().includes(lowerUrl)) {
|
|
1062
|
+
matchingEntryIds.push(vault.blockIds[i]);
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
// Decrypt matching entries and build payload (Req 3.2)
|
|
1066
|
+
const entries = [];
|
|
1067
|
+
for (const entryId of matchingEntryIds) {
|
|
1068
|
+
const encrypted = vault.entries.get(entryId);
|
|
1069
|
+
if (!encrypted)
|
|
1070
|
+
continue;
|
|
1071
|
+
const decrypted = vaultEncryption_1.VaultEncryption.decryptString(vault.vaultKey, encrypted);
|
|
1072
|
+
const entry = brightchain_lib_1.VaultSerializer.deserializeEntry(decrypted);
|
|
1073
|
+
if (entry.type !== 'login')
|
|
1074
|
+
continue;
|
|
1075
|
+
const loginEntry = entry;
|
|
1076
|
+
let totpCode;
|
|
1077
|
+
if (loginEntry.totpSecret) {
|
|
1078
|
+
totpCode = brightchain_lib_1.TOTPEngine.generate(loginEntry.totpSecret);
|
|
1079
|
+
}
|
|
1080
|
+
entries.push({
|
|
1081
|
+
entryId: loginEntry.id,
|
|
1082
|
+
title: loginEntry.title,
|
|
1083
|
+
username: loginEntry.username,
|
|
1084
|
+
password: loginEntry.password,
|
|
1085
|
+
siteUrl: loginEntry.siteUrl,
|
|
1086
|
+
totpCode,
|
|
1087
|
+
});
|
|
1088
|
+
}
|
|
1089
|
+
return { vaultId, entries };
|
|
1090
|
+
}
|
|
1091
|
+
// ─── Attachments ──────────────────────────────────────────────
|
|
1092
|
+
/**
|
|
1093
|
+
* Add an attachment to a vault entry.
|
|
1094
|
+
* Stores the file as an encrypted block and adds a reference to the entry.
|
|
1095
|
+
* Requirements: 1.7, 2.8
|
|
1096
|
+
*/
|
|
1097
|
+
async addAttachment(vaultId, entryId, file, filename, mimeType = 'application/octet-stream') {
|
|
1098
|
+
const vault = this.getVaultOrThrow(vaultId);
|
|
1099
|
+
const encrypted = vault.entries.get(entryId);
|
|
1100
|
+
if (!encrypted) {
|
|
1101
|
+
throw new EntryNotFoundError(entryId);
|
|
1102
|
+
}
|
|
1103
|
+
// Decrypt entry (Req 3.2)
|
|
1104
|
+
const decrypted = vaultEncryption_1.VaultEncryption.decryptString(vault.vaultKey, encrypted);
|
|
1105
|
+
const entry = brightchain_lib_1.VaultSerializer.deserializeEntry(decrypted);
|
|
1106
|
+
// Create attachment reference
|
|
1107
|
+
const attachmentId = (0, uuid_1.v4)();
|
|
1108
|
+
const blockId = (0, uuid_1.v4)(); // simulated block storage
|
|
1109
|
+
const reference = {
|
|
1110
|
+
id: attachmentId,
|
|
1111
|
+
filename,
|
|
1112
|
+
mimeType,
|
|
1113
|
+
sizeBytes: file.length,
|
|
1114
|
+
blockId,
|
|
1115
|
+
isCbl: file.length > 65536, // use CBL for files > 64KB
|
|
1116
|
+
};
|
|
1117
|
+
// Encrypt and store the attachment data (Req 3.2)
|
|
1118
|
+
const encryptedFile = vaultEncryption_1.VaultEncryption.encrypt(vault.vaultKey, new Uint8Array(file));
|
|
1119
|
+
await this.blockStore.put(blockId, encryptedFile);
|
|
1120
|
+
// Update entry with attachment reference
|
|
1121
|
+
const attachments = entry.attachments ?? [];
|
|
1122
|
+
attachments.push(reference);
|
|
1123
|
+
const updatedEntry = {
|
|
1124
|
+
...entry,
|
|
1125
|
+
attachments,
|
|
1126
|
+
updatedAt: new Date(),
|
|
1127
|
+
};
|
|
1128
|
+
const serialized = brightchain_lib_1.VaultSerializer.serializeEntry(updatedEntry);
|
|
1129
|
+
vault.entries.set(entryId, vaultEncryption_1.VaultEncryption.encryptString(vault.vaultKey, serialized));
|
|
1130
|
+
// Update property record timestamp
|
|
1131
|
+
const index = vault.blockIds.indexOf(entryId);
|
|
1132
|
+
if (index !== -1) {
|
|
1133
|
+
vault.propertyRecords[index] = this.entryToPropertyRecord(updatedEntry);
|
|
1134
|
+
}
|
|
1135
|
+
vault.metadata.updatedAt = new Date();
|
|
1136
|
+
await vault.auditLogger.log({
|
|
1137
|
+
id: (0, uuid_1.v4)(),
|
|
1138
|
+
vaultId,
|
|
1139
|
+
memberId: vault.metadata.ownerId,
|
|
1140
|
+
action: brightchain_lib_1.AuditAction.ENTRY_UPDATED,
|
|
1141
|
+
metadata: { entryId, attachmentId, filename },
|
|
1142
|
+
});
|
|
1143
|
+
return reference;
|
|
1144
|
+
}
|
|
1145
|
+
/**
|
|
1146
|
+
* Get an attachment's data by ID.
|
|
1147
|
+
* Decrypts the attachment using AES-256-GCM.
|
|
1148
|
+
* Requirements: 2.8, 3.2
|
|
1149
|
+
*/
|
|
1150
|
+
async getAttachment(vaultId, entryId, attachmentId) {
|
|
1151
|
+
const vault = this.getVaultOrThrow(vaultId);
|
|
1152
|
+
const encrypted = vault.entries.get(entryId);
|
|
1153
|
+
if (!encrypted) {
|
|
1154
|
+
throw new EntryNotFoundError(entryId);
|
|
1155
|
+
}
|
|
1156
|
+
// Decrypt entry to get attachment reference (Req 3.2)
|
|
1157
|
+
const decrypted = vaultEncryption_1.VaultEncryption.decryptString(vault.vaultKey, encrypted);
|
|
1158
|
+
const entry = brightchain_lib_1.VaultSerializer.deserializeEntry(decrypted);
|
|
1159
|
+
const ref = entry.attachments?.find((a) => a.id === attachmentId);
|
|
1160
|
+
if (!ref) {
|
|
1161
|
+
throw new EntryNotFoundError(`Attachment not found: ${attachmentId}`);
|
|
1162
|
+
}
|
|
1163
|
+
const hasBlock = await this.blockStore.has(ref.blockId);
|
|
1164
|
+
if (!hasBlock) {
|
|
1165
|
+
throw new EntryNotFoundError(`Attachment data not found: ${attachmentId}`);
|
|
1166
|
+
}
|
|
1167
|
+
const blockHandle = this.blockStore.get(ref.blockId);
|
|
1168
|
+
const encryptedData = blockHandle.fullData;
|
|
1169
|
+
// Decrypt attachment data (Req 3.2)
|
|
1170
|
+
const decryptedData = vaultEncryption_1.VaultEncryption.decrypt(vault.vaultKey, new Uint8Array(encryptedData));
|
|
1171
|
+
return Buffer.from(decryptedData);
|
|
1172
|
+
}
|
|
1173
|
+
/**
|
|
1174
|
+
* Delete an attachment from a vault entry.
|
|
1175
|
+
* Removes the attachment block and updates the entry reference.
|
|
1176
|
+
* Requirements: 2.8, 3.2
|
|
1177
|
+
*/
|
|
1178
|
+
async deleteAttachment(vaultId, entryId, attachmentId) {
|
|
1179
|
+
const vault = this.getVaultOrThrow(vaultId);
|
|
1180
|
+
const encrypted = vault.entries.get(entryId);
|
|
1181
|
+
if (!encrypted) {
|
|
1182
|
+
throw new EntryNotFoundError(entryId);
|
|
1183
|
+
}
|
|
1184
|
+
// Decrypt entry (Req 3.2)
|
|
1185
|
+
const decrypted = vaultEncryption_1.VaultEncryption.decryptString(vault.vaultKey, encrypted);
|
|
1186
|
+
const entry = brightchain_lib_1.VaultSerializer.deserializeEntry(decrypted);
|
|
1187
|
+
const attachments = entry.attachments ?? [];
|
|
1188
|
+
const refIndex = attachments.findIndex((a) => a.id === attachmentId);
|
|
1189
|
+
if (refIndex === -1) {
|
|
1190
|
+
throw new EntryNotFoundError(`Attachment not found: ${attachmentId}`);
|
|
1191
|
+
}
|
|
1192
|
+
// Remove from block store
|
|
1193
|
+
const ref = attachments[refIndex];
|
|
1194
|
+
await this.blockStore.delete(ref.blockId);
|
|
1195
|
+
// Update entry and re-encrypt (Req 3.2)
|
|
1196
|
+
attachments.splice(refIndex, 1);
|
|
1197
|
+
const updatedEntry = {
|
|
1198
|
+
...entry,
|
|
1199
|
+
attachments: attachments.length > 0 ? attachments : undefined,
|
|
1200
|
+
updatedAt: new Date(),
|
|
1201
|
+
};
|
|
1202
|
+
const serialized = brightchain_lib_1.VaultSerializer.serializeEntry(updatedEntry);
|
|
1203
|
+
vault.entries.set(entryId, vaultEncryption_1.VaultEncryption.encryptString(vault.vaultKey, serialized));
|
|
1204
|
+
// Update property record timestamp
|
|
1205
|
+
const index = vault.blockIds.indexOf(entryId);
|
|
1206
|
+
if (index !== -1) {
|
|
1207
|
+
vault.propertyRecords[index] = this.entryToPropertyRecord(updatedEntry);
|
|
1208
|
+
}
|
|
1209
|
+
vault.metadata.updatedAt = new Date();
|
|
1210
|
+
await vault.auditLogger.log({
|
|
1211
|
+
id: (0, uuid_1.v4)(),
|
|
1212
|
+
vaultId,
|
|
1213
|
+
memberId: vault.metadata.ownerId,
|
|
1214
|
+
action: brightchain_lib_1.AuditAction.ENTRY_UPDATED,
|
|
1215
|
+
metadata: { entryId, attachmentId, action: 'delete_attachment' },
|
|
1216
|
+
});
|
|
1217
|
+
}
|
|
1218
|
+
// ─── Import ───────────────────────────────────────────────────
|
|
1219
|
+
/**
|
|
1220
|
+
* Import entries from a password manager export file.
|
|
1221
|
+
* Parses the file, maps to VaultEntry types, and adds each to the vault.
|
|
1222
|
+
* Requirements: 12.1–12.10
|
|
1223
|
+
*/
|
|
1224
|
+
async importFromFile(vaultId, format, fileContent) {
|
|
1225
|
+
this.getVaultOrThrow(vaultId); // verify vault exists
|
|
1226
|
+
const { entries, errors } = brightchain_lib_1.ImportParser.parse(format, fileContent.buffer.slice(fileContent.byteOffset, fileContent.byteOffset + fileContent.byteLength));
|
|
1227
|
+
const importErrors = [...errors];
|
|
1228
|
+
let successfulImports = 0;
|
|
1229
|
+
for (let i = 0; i < entries.length; i++) {
|
|
1230
|
+
try {
|
|
1231
|
+
await this.addEntry(vaultId, entries[i]);
|
|
1232
|
+
successfulImports++;
|
|
1233
|
+
}
|
|
1234
|
+
catch (err) {
|
|
1235
|
+
importErrors.push({
|
|
1236
|
+
recordIndex: i,
|
|
1237
|
+
error: err instanceof Error ? err.message : String(err),
|
|
1238
|
+
});
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1241
|
+
return {
|
|
1242
|
+
totalRecords: entries.length + errors.length,
|
|
1243
|
+
successfulImports,
|
|
1244
|
+
errors: importErrors,
|
|
1245
|
+
};
|
|
1246
|
+
}
|
|
1247
|
+
// ─── Audit ────────────────────────────────────────────────────
|
|
1248
|
+
/**
|
|
1249
|
+
* Get audit log entries for a vault.
|
|
1250
|
+
* Requirements: 9.3
|
|
1251
|
+
*/
|
|
1252
|
+
async getAuditLog(vaultId) {
|
|
1253
|
+
const vault = this.getVaultOrThrow(vaultId);
|
|
1254
|
+
return vault.auditLogger.getEntries();
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1257
|
+
exports.BrightPassService = BrightPassService;
|
|
1258
|
+
/** bcrypt cost factor - 12 rounds provides ~300ms hash time on modern hardware */
|
|
1259
|
+
BrightPassService.BCRYPT_ROUNDS = 12;
|
|
1260
|
+
//# sourceMappingURL=brightpass.js.map
|