release: prepare v4.8.5 security hardening release
This commit is contained in:
@@ -0,0 +1,112 @@
|
||||
import assert from 'node:assert/strict';
|
||||
|
||||
globalThis.window = { EnhancedSecureCryptoUtils: {} };
|
||||
const { SecureIndexedDBWrapper, SecurePersistentKeyStorage } = await import('../src/network/EnhancedSecureWebRTCManager.js');
|
||||
|
||||
class FakeMasterKeyManager {
|
||||
isUnlocked() { return true; }
|
||||
async unlock() {}
|
||||
async encryptBytes(bytes) {
|
||||
return { encryptedData: Uint8Array.from(bytes, byte => byte ^ 0xaa), iv: new Uint8Array(12).fill(7) };
|
||||
}
|
||||
async decryptBytes(bytes, iv) {
|
||||
if (!iv || iv[0] !== 7) throw new Error('bad iv');
|
||||
return Uint8Array.from(bytes, byte => byte ^ 0xaa);
|
||||
}
|
||||
}
|
||||
|
||||
class FakeDB {
|
||||
constructor(records = []) {
|
||||
this.records = new Map(records.map(record => [record.keyId, record]));
|
||||
}
|
||||
async initialize() {}
|
||||
async listKeys() { return [...this.records.values()]; }
|
||||
async getKeyMetadataRecord(keyId) { return this.records.get(keyId) || null; }
|
||||
async putKeyMetadataRecord(record) { this.records.set(record.keyId, record); }
|
||||
}
|
||||
|
||||
class FakeIndexedDBConnection {
|
||||
constructor() {
|
||||
this.records = new Map();
|
||||
}
|
||||
transaction(storeNames) {
|
||||
const transaction = {
|
||||
objectStore: (name) => ({
|
||||
put: (record) => {
|
||||
this.records.set(name, record);
|
||||
queueMicrotask(() => transaction.oncomplete?.());
|
||||
return {};
|
||||
}
|
||||
}),
|
||||
oncomplete: null,
|
||||
onerror: null
|
||||
};
|
||||
return transaction;
|
||||
}
|
||||
}
|
||||
|
||||
// New metadata is encrypted and sensitive fields are not plaintext.
|
||||
{
|
||||
const db = new FakeDB();
|
||||
const storage = new SecurePersistentKeyStorage(new FakeMasterKeyManager(), db);
|
||||
const encrypted = await storage._encryptMetadata({
|
||||
created: 111,
|
||||
lastAccessed: 222,
|
||||
sessionId: 'session-secret',
|
||||
peerId: 'peer-secret'
|
||||
});
|
||||
await db.putKeyMetadataRecord({ keyId: 'k1', ...encrypted });
|
||||
const raw = db.records.get('k1');
|
||||
assert.equal(raw.created, undefined);
|
||||
assert.equal(raw.lastAccessed, undefined);
|
||||
assert.equal(raw.sessionId, undefined);
|
||||
assert.ok(Array.isArray(raw.encryptedMetadata));
|
||||
}
|
||||
|
||||
// Old plaintext metadata can be read and is migrated.
|
||||
{
|
||||
const db = new FakeDB([{ keyId: 'legacy', created: 1, lastAccessed: 2, sessionId: 'old-session' }]);
|
||||
const storage = new SecurePersistentKeyStorage(new FakeMasterKeyManager(), db);
|
||||
const listed = await storage.listStoredKeys();
|
||||
assert.equal(listed[0].sessionId, 'old-session');
|
||||
const migrated = db.records.get('legacy');
|
||||
assert.equal(migrated.sessionId, undefined);
|
||||
assert.ok(migrated.encryptedMetadata);
|
||||
}
|
||||
|
||||
// Corrupted encrypted metadata fails safely and is not exposed.
|
||||
{
|
||||
const db = new FakeDB([{ keyId: 'bad', encryptedMetadata: [1, 2, 3], metadataIv: [0] }]);
|
||||
const storage = new SecurePersistentKeyStorage(new FakeMasterKeyManager(), db);
|
||||
const listed = await storage.listStoredKeys();
|
||||
assert.deepEqual(listed, []);
|
||||
}
|
||||
|
||||
// Plaintext timestamp fields are avoidable in the encrypted envelope.
|
||||
{
|
||||
const storage = new SecurePersistentKeyStorage(new FakeMasterKeyManager(), new FakeDB());
|
||||
const encrypted = await storage._encryptMetadata({ created: 10, lastAccessed: 20, usageCount: 3 });
|
||||
assert.equal('created' in encrypted, false);
|
||||
assert.equal('lastAccessed' in encrypted, false);
|
||||
assert.equal('usageCount' in encrypted, false);
|
||||
}
|
||||
|
||||
// Avoidable timestamps are not left plaintext in new IndexedDB records.
|
||||
{
|
||||
const wrapper = new SecureIndexedDBWrapper();
|
||||
wrapper.db = new FakeIndexedDBConnection();
|
||||
await wrapper.storeEncryptedKey(
|
||||
'k2',
|
||||
new Uint8Array([1]),
|
||||
new Uint8Array([2]),
|
||||
{ name: 'AES-GCM' },
|
||||
['encrypt'],
|
||||
'secret',
|
||||
{ metadataVersion: 1, encryptedMetadata: [3], metadataIv: [4] }
|
||||
);
|
||||
assert.equal('timestamp' in wrapper.db.records.get(wrapper.KEYS_STORE), false);
|
||||
assert.equal('created' in wrapper.db.records.get(wrapper.METADATA_STORE), false);
|
||||
assert.equal('lastAccessed' in wrapper.db.records.get(wrapper.METADATA_STORE), false);
|
||||
}
|
||||
|
||||
console.log('IndexedDB metadata encryption tests passed');
|
||||
Reference in New Issue
Block a user