Files

113 lines
4.1 KiB
JavaScript
Raw Permalink Normal View History

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');