feat(security): encrypted key storage with WeakMap and SecureKeyStorage

- Added SecureKeyStorage class: JWK storage encrypted (AES-GCM) + WeakMap
- Support for non-extractable keys via secure links without export
- Implemented secure wipe/secure wipe all, statistics collection
- Improved secure logging: blacklist of sensitive fields, whitelist of secure ones
- Integration with WebRTC manager layers and secure key installation
This commit is contained in:
lockbitchat
2025-08-28 16:17:40 -04:00
parent a4161bc47e
commit 97b87828e2

View File

@@ -448,14 +448,18 @@ _initializeMutexSystem() {
* Initializes the secure key storage * Initializes the secure key storage
*/ */
_initializeSecureKeyStorage() { _initializeSecureKeyStorage() {
this._secureKeyStorage = new Map(); // Initialize with the new class
this._secureKeyStorage = new SecureKeyStorage();
// Keep the stats structure for compatibility
this._keyStorageStats = { this._keyStorageStats = {
totalKeys: 0, totalKeys: 0,
activeKeys: 0, activeKeys: 0,
lastAccess: null, lastAccess: null,
lastRotation: null, lastRotation: null,
}; };
this._secureLog('info', '🔐 Secure key storage initialized');
this._secureLog('info', '🔐 Enhanced secure key storage initialized');
} }
// Helper: ensure file transfer system is ready (lazy init on receiver) // Helper: ensure file transfer system is ready (lazy init on receiver)
@@ -497,35 +501,26 @@ _initializeMutexSystem() {
} }
} }
/**
* Retrieves a key from secure storage
* @param {string} keyId - Key identifier
* @returns {CryptoKey|null} The key or null if not found
*/
_getSecureKey(keyId) { _getSecureKey(keyId) {
if (!this._secureKeyStorage.has(keyId)) { return this._secureKeyStorage.retrieveKey(keyId);
this._secureLog('warn', `⚠️ Key ${keyId} not found in secure storage`);
return null;
}
this._keyStorageStats.lastAccess = Date.now();
return this._secureKeyStorage.get(keyId);
} }
/** async _setSecureKey(keyId, key) {
* Stores a key in secure storage
* @param {string} keyId - Key identifier
* @param {CryptoKey} key - Key to store
*/
_setSecureKey(keyId, key) {
if (!(key instanceof CryptoKey)) { if (!(key instanceof CryptoKey)) {
this._secureLog('error', '❌ Attempt to store non-CryptoKey in secure storage'); this._secureLog('error', '❌ Attempt to store non-CryptoKey');
return; return false;
} }
this._secureKeyStorage.set(keyId, key);
this._keyStorageStats.totalKeys++; const success = await this._secureKeyStorage.storeKey(keyId, key, {
this._keyStorageStats.activeKeys++; version: this.currentKeyVersion,
this._keyStorageStats.lastAccess = Date.now(); type: key.algorithm.name
this._secureLog('info', `🔑 Key ${keyId} stored securely`); });
if (success) {
this._secureLog('info', `🔑 Key ${keyId} stored securely with encryption`);
}
return success;
} }
/** /**
@@ -540,18 +535,9 @@ _initializeMutexSystem() {
key.usages.length > 0; key.usages.length > 0;
} }
/**
* Securely wipes all keys from storage
*/
_secureWipeKeys() { _secureWipeKeys() {
this._secureKeyStorage.clear(); this._secureKeyStorage.secureWipeAll();
this._keyStorageStats = { this._secureLog('info', '🧹 All keys securely wiped and encrypted storage cleared');
totalKeys: 0,
activeKeys: 0,
lastAccess: null,
lastRotation: null,
};
this._secureLog('info', '🧹 All keys securely wiped from storage');
} }
/** /**
@@ -559,7 +545,7 @@ _initializeMutexSystem() {
* @returns {boolean} true if the storage is ready * @returns {boolean} true if the storage is ready
*/ */
_validateKeyStorage() { _validateKeyStorage() {
return this._secureKeyStorage instanceof Map; return this._secureKeyStorage instanceof SecureKeyStorage;
} }
/** /**
@@ -567,12 +553,13 @@ _initializeMutexSystem() {
* @returns {object} Storage metrics * @returns {object} Storage metrics
*/ */
_getKeyStorageStats() { _getKeyStorageStats() {
const stats = this._secureKeyStorage.getStorageStats();
return { return {
totalKeysCount: this._keyStorageStats.totalKeys, totalKeysCount: stats.totalKeys,
activeKeysCount: this._keyStorageStats.activeKeys, activeKeysCount: stats.totalKeys,
hasLastAccess: !!this._keyStorageStats.lastAccess, hasLastAccess: stats.metadata.some(m => m.lastAccessed),
hasLastRotation: !!this._keyStorageStats.lastRotation, hasLastRotation: !!this._keyStorageStats.lastRotation,
storageType: 'SecureMap', storageType: 'SecureKeyStorage',
timestamp: Date.now() timestamp: Date.now()
}; };
} }
@@ -7412,4 +7399,211 @@ checkFileTransferReadiness() {
} }
} }
class SecureKeyStorage {
constructor() {
// Use WeakMap for automatic garbage collection of unused keys
this._keyStore = new WeakMap();
this._keyMetadata = new Map(); // Metadata doesn't need WeakMap
this._keyReferences = new Map(); // Strong references for active keys
// Master encryption key for storage encryption
this._storageMasterKey = null;
this._initializeStorageMaster();
}
async _initializeStorageMaster() {
// Generate a master key for encrypting stored keys
this._storageMasterKey = await crypto.subtle.generateKey(
{ name: 'AES-GCM', length: 256 },
false,
['encrypt', 'decrypt']
);
}
async storeKey(keyId, cryptoKey, metadata = {}) {
if (!(cryptoKey instanceof CryptoKey)) {
throw new Error('Only CryptoKey objects can be stored');
}
try {
// For non-extractable keys, we can only store a reference
if (!cryptoKey.extractable) {
// Store the key reference directly without encryption
this._keyReferences.set(keyId, cryptoKey);
this._keyMetadata.set(keyId, {
...metadata,
created: Date.now(),
lastAccessed: Date.now(),
extractable: false,
encrypted: false // Mark as not encrypted
});
return true;
}
// For extractable keys, proceed with encryption
const keyData = await crypto.subtle.exportKey('jwk', cryptoKey);
const encryptedKeyData = await this._encryptKeyData(keyData);
// Create a storage object
const storageObject = {
id: keyId,
encryptedData: encryptedKeyData,
algorithm: cryptoKey.algorithm,
usages: cryptoKey.usages,
extractable: cryptoKey.extractable,
type: cryptoKey.type,
timestamp: Date.now()
};
// Use WeakMap with the CryptoKey as the key
this._keyStore.set(cryptoKey, storageObject);
// Store reference for retrieval by ID
this._keyReferences.set(keyId, cryptoKey);
// Store metadata separately
this._keyMetadata.set(keyId, {
...metadata,
created: Date.now(),
lastAccessed: Date.now()
});
return true;
} catch (error) {
console.error('Failed to store key securely:', error);
return false;
}
}
async retrieveKey(keyId) {
const metadata = this._keyMetadata.get(keyId);
if (!metadata) {
return null;
}
// Update access time
metadata.lastAccessed = Date.now();
// For non-encrypted keys (non-extractable), return directly
if (!metadata.encrypted) {
return this._keyReferences.get(keyId);
}
// For encrypted keys, decrypt and recreate
try {
const cryptoKey = this._keyReferences.get(keyId);
const storedData = this._keyStore.get(cryptoKey);
if (!storedData) {
return null;
}
// Decrypt the key data
const decryptedKeyData = await this._decryptKeyData(storedData.encryptedData);
// Recreate the CryptoKey
const recreatedKey = await crypto.subtle.importKey(
'jwk',
decryptedKeyData,
storedData.algorithm,
storedData.extractable,
storedData.usages
);
return recreatedKey;
} catch (error) {
console.error('Failed to retrieve key:', error);
return null;
}
}
async _encryptKeyData(keyData) {
const dataToEncrypt = typeof keyData === 'object'
? JSON.stringify(keyData)
: keyData;
const encoder = new TextEncoder();
const data = encoder.encode(dataToEncrypt);
const iv = crypto.getRandomValues(new Uint8Array(12));
const encryptedData = await crypto.subtle.encrypt(
{ name: 'AES-GCM', iv },
this._storageMasterKey,
data
);
// Return IV + encrypted data
const result = new Uint8Array(iv.length + encryptedData.byteLength);
result.set(iv, 0);
result.set(new Uint8Array(encryptedData), iv.length);
return result;
}
async _decryptKeyData(encryptedData) {
const iv = encryptedData.slice(0, 12);
const data = encryptedData.slice(12);
const decryptedData = await crypto.subtle.decrypt(
{ name: 'AES-GCM', iv },
this._storageMasterKey,
data
);
const decoder = new TextDecoder();
const jsonString = decoder.decode(decryptedData);
try {
return JSON.parse(jsonString);
} catch {
return decryptedData;
}
}
secureWipe(keyId) {
const cryptoKey = this._keyReferences.get(keyId);
if (cryptoKey) {
// Remove from WeakMap (will be GC'd)
this._keyStore.delete(cryptoKey);
// Remove strong reference
this._keyReferences.delete(keyId);
// Remove metadata
this._keyMetadata.delete(keyId);
}
// Overwrite memory locations if possible
if (typeof window.gc === 'function') {
window.gc();
}
}
secureWipeAll() {
// Clear all references
this._keyReferences.clear();
this._keyMetadata.clear();
// WeakMap entries will be garbage collected
this._keyStore = new WeakMap();
// Force garbage collection if available
if (typeof window.gc === 'function') {
window.gc();
}
}
getStorageStats() {
return {
totalKeys: this._keyReferences.size,
metadata: Array.from(this._keyMetadata.entries()).map(([id, meta]) => ({
id,
created: meta.created,
lastAccessed: meta.lastAccessed,
age: Date.now() - meta.created
}))
};
}
}
export { EnhancedSecureWebRTCManager }; export { EnhancedSecureWebRTCManager };