-
Notifications
You must be signed in to change notification settings - Fork 0
Description
Implement Secure Storage Manager for Browser Extension
Description:
Create a secure storage manager that wraps chrome.storage / browser.storage APIs with automatic encryption for sensitive data (private keys, account state). Must work seamlessly across Chrome and Firefox with transparent encryption/decryption.
Context:
Browser extensions use chrome.storage.local for persistent data storage. However, this storage is NOT encrypted by default - any data stored is accessible to the extension and potentially to malware or other extensions with storage permissions. We need a wrapper that:
- Automatically encrypts sensitive data before storage
- Derives encryption keys from user passwords (never stores keys)
- Works across Chrome (
chrome.storage) and Firefox (browser.storage) - Handles session timeouts and re-authentication
- Provides a clean API for account and session key storage
Important Security Principle: Encryption keys must NEVER be stored. They should be derived from the user's password using PBKDF2 and kept in memory only during the active session.
Requirements:
- Create
SecureStorageManagerclass with cross-browser support (chrome.storage / browser.storage) - Implement Web Crypto API encryption (PBKDF2 + AES-GCM)
- Derive encryption keys from user password (100k iterations, never store keys)
- Generate unique IV for each encryption operation
- Implement
saveAccount()with automatic private key encryption - Implement
getAccount()with automatic decryption - Session key storage (encrypted)
- Session timeout and auto-lock functionality
- Clear sensitive data from memory after use
- Storage quota monitoring and error handling
- Export/import for backup (encrypted format)
- Unit tests with mocked chrome.storage API (>85% coverage)
- Browser compatibility tests (Chrome + Firefox)
Implementation Guide:
// 1. Cross-browser storage wrapper
const storage = typeof browser !== 'undefined'
? browser.storage
: chrome.storage;
export class SecureStorageManager {
private encryptionKey: CryptoKey | null = null;
private salt: Uint8Array | null = null;
private sessionTimeout: number = 15 * 60 * 1000; // 15 minutes
private lastActivity: number = Date.now();
constructor() {
// Set up auto-lock on inactivity
setInterval(() => this.checkSessionTimeout(), 60000);
}
// 2. Derive encryption key from password (NEVER store this key!)
private async deriveKey(password: string, salt: Uint8Array): Promise<CryptoKey> {
const encoder = new TextEncoder();
const keyMaterial = await crypto.subtle.importKey(
'raw',
encoder.encode(password),
'PBKDF2',
false,
['deriveBits', 'deriveKey']
);
return crypto.subtle.deriveKey(
{
name: 'PBKDF2',
salt: salt,
iterations: 100_000, // 100k iterations
hash: 'SHA-256'
},
keyMaterial,
{ name: 'AES-GCM', length: 256 },
false,
['encrypt', 'decrypt']
);
}
// 3. Encrypt data with AES-GCM
private async encryptData(data: any): Promise<{
encrypted: number[];
iv: number[];
}> {
if (!this.encryptionKey) {
throw new Error('Storage locked. Please unlock first.');
}
const encoder = new TextEncoder();
const iv = crypto.getRandomValues(new Uint8Array(12)); // Unique IV
const encrypted = await crypto.subtle.encrypt(
{ name: 'AES-GCM', iv: iv },
this.encryptionKey,
encoder.encode(JSON.stringify(data))
);
return {
encrypted: Array.from(new Uint8Array(encrypted)),
iv: Array.from(iv)
};
}
// 4. Decrypt data
private async decryptData(
encryptedData: number[],
iv: number[]
): Promise<any> {
if (!this.encryptionKey) {
throw new Error('Storage locked. Please unlock first.');
}
const decrypted = await crypto.subtle.decrypt(
{ name: 'AES-GCM', iv: new Uint8Array(iv) },
this.encryptionKey,
new Uint8Array(encryptedData)
);
const decoder = new TextDecoder();
return JSON.parse(decoder.decode(decrypted));
}
// 5. Unlock storage with password
async unlock(password: string): Promise<boolean> {
try {
// Get or generate salt
const stored = await storage.local.get('salt');
if (!stored.salt) {
// First time - generate salt
this.salt = crypto.getRandomValues(new Uint8Array(16));
await storage.local.set({ salt: Array.from(this.salt) });
} else {
this.salt = new Uint8Array(stored.salt);
}
// Derive key from password
this.encryptionKey = await this.deriveKey(password, this.salt);
this.lastActivity = Date.now();
// Verify password by trying to decrypt existing data
const test = await storage.local.get('accountData');
if (test.accountData) {
await this.decryptData(
test.accountData.encrypted,
test.accountData.iv
);
}
return true;
} catch (error) {
this.encryptionKey = null;
return false;
}
}
// 6. Lock storage (clear key from memory)
lock(): void {
this.encryptionKey = null;
this.salt = null;
}
// 7. Save account with encrypted secret key
async saveAccount(account: {
publicKey: string;
secretKey: string; // Will be encrypted
contractId: string;
nonce: number;
}): Promise<void> {
this.updateActivity();
const encrypted = await this.encryptData(account);
await storage.local.set({ accountData: encrypted });
}
// 8. Get account (auto-decrypt)
async getAccount(): Promise<any> {
this.updateActivity();
const result = await storage.local.get('accountData');
if (!result.accountData) return null;
return await this.decryptData(
result.accountData.encrypted,
result.accountData.iv
);
}
// 9. Save session keys (encrypted)
async saveSessionKeys(keys: any[]): Promise<void> {
this.updateActivity();
const encrypted = await this.encryptData(keys);
await storage.local.set({ sessionKeys: encrypted });
}
// 10. Get session keys (auto-decrypt)
async getSessionKeys(): Promise<any[]> {
this.updateActivity();
const result = await storage.local.get('sessionKeys');
if (!result.sessionKeys) return [];
return await this.decryptData(
result.sessionKeys.encrypted,
result.sessionKeys.iv
);
}
// 11. Auto-lock on inactivity
private checkSessionTimeout(): void {
if (this.encryptionKey && Date.now() - this.lastActivity > this.sessionTimeout) {
this.lock();
console.log('Storage auto-locked due to inactivity');
}
}
private updateActivity(): void {
this.lastActivity = Date.now();
}
// 12. Clear all data
async clear(): Promise<void> {
await storage.local.clear();
this.lock();
}
// 13. Export encrypted backup
async export(): Promise<string> {
const data = await storage.local.get(null); // Get all data
return JSON.stringify(data);
}
// 14. Import encrypted backup
async import(backup: string): Promise<void> {
const data = JSON.parse(backup);
await storage.local.set(data);
}
}Usage:
import { SecureStorageManager } from '@ancore/core-sdk';
const storage = new SecureStorageManager();
// Unlock with user password
const unlocked = await storage.unlock('userPassword123!');
if (!unlocked) {
throw new Error('Invalid password');
}
// Save account (secret key encrypted automatically)
await storage.saveAccount({
publicKey: keypair.publicKey(),
secretKey: keypair.secret(), // Will be encrypted
contractId: 'CABC...',
nonce: 0
});
// Get account (auto-decrypts)
const account = await storage.getAccount();
console.log(account.publicKey); // Decrypted
// Lock storage (clears key from memory)
storage.lock();
// Later, unlock again
await storage.unlock('userPassword123!');Files to Create:
packages/core-sdk/src/storage/secure-storage-manager.ts(main class)packages/core-sdk/src/storage/types.ts(EncryptedData type)packages/core-sdk/src/storage/__tests__/manager.test.ts(unit tests)packages/core-sdk/src/storage/__tests__/chrome-compat.test.ts(browser tests)apps/extension-wallet/src/background/storage.ts(extension integration)
Dependencies:
- Web Crypto API (built-in browser API, no external deps)
@types/chrome(TypeScript types for chrome.storage)webextension-polyfill(optional, for Firefox compatibility)
Note: This implementation uses Web Crypto API (built-in) instead of external crypto libraries to reduce bundle size and leverage browser-native security features.
Success Criteria:
- Works in both Chrome and Firefox without polyfills
- Encryption keys NEVER stored (only derived from password)
- Session timeout auto-locks storage after 15 minutes of inactivity
- Handles wrong password gracefully (returns false, doesn't crash)
- Handles storage quota exceeded errors
- No sensitive data remains in memory after lock()
- Export/import preserves encrypted format
Definition of Done:
- All requirements implemented with Web Crypto API
- Tested in Chrome 90+ and Firefox 90+
- Encryption verified: PBKDF2 (100k iterations) + AES-256-GCM
- Session timeout works correctly
- Unit tests with mocked chrome.storage (>85% coverage)
- Manual browser testing completed
- Security audit checklist passed (no key storage, unique IVs, proper salt)
Security Checklist:
- Encryption key never stored (only in memory during session)
- Unique IV generated for each encryption
- Salt generated on first use and persisted (non-sensitive)
- PBKDF2 with 100,000 iterations
- AES-256-GCM for authenticated encryption
- Auto-lock after 15 minutes of inactivity
- Memory cleared on lock() call
Additional Resources:
Labels: storage, sdk, security, extension
Complexity: 200 points (High)
Estimated Effort: 3-4 days
Priority: High
Questions or need help? Join our Telegram community