|
|
|
|
|
|
|
|
|
|
|
|
|
|
const ENCRYPTION_KEY_NAME = 'mcp-encryption-key'; |
|
|
|
|
|
|
|
|
async function getEncryptionKey(): Promise<CryptoKey> { |
|
|
const keyData = localStorage.getItem(ENCRYPTION_KEY_NAME); |
|
|
|
|
|
if (keyData) { |
|
|
try { |
|
|
const keyBuffer = new Uint8Array(JSON.parse(keyData)); |
|
|
return await crypto.subtle.importKey( |
|
|
'raw', |
|
|
keyBuffer, |
|
|
{ name: 'AES-GCM' }, |
|
|
false, |
|
|
['encrypt', 'decrypt'] |
|
|
); |
|
|
} catch { |
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
const key = await crypto.subtle.generateKey( |
|
|
{ name: 'AES-GCM', length: 256 }, |
|
|
true, |
|
|
['encrypt', 'decrypt'] |
|
|
); |
|
|
|
|
|
|
|
|
const keyBuffer = await crypto.subtle.exportKey('raw', key); |
|
|
localStorage.setItem(ENCRYPTION_KEY_NAME, JSON.stringify(Array.from(new Uint8Array(keyBuffer)))); |
|
|
|
|
|
return key; |
|
|
} |
|
|
|
|
|
|
|
|
export async function encryptData(data: string): Promise<string> { |
|
|
try { |
|
|
const key = await getEncryptionKey(); |
|
|
const encoder = new TextEncoder(); |
|
|
const dataBuffer = encoder.encode(data); |
|
|
|
|
|
const iv = crypto.getRandomValues(new Uint8Array(12)); |
|
|
const encryptedBuffer = await crypto.subtle.encrypt( |
|
|
{ name: 'AES-GCM', iv }, |
|
|
key, |
|
|
dataBuffer |
|
|
); |
|
|
|
|
|
|
|
|
const result = new Uint8Array(iv.length + encryptedBuffer.byteLength); |
|
|
result.set(iv); |
|
|
result.set(new Uint8Array(encryptedBuffer), iv.length); |
|
|
|
|
|
return btoa(String.fromCharCode(...result)); |
|
|
} catch (error) { |
|
|
console.warn('Encryption failed, storing data unencrypted:', error); |
|
|
return data; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
export async function decryptData(encryptedData: string): Promise<string> { |
|
|
try { |
|
|
const key = await getEncryptionKey(); |
|
|
const dataBuffer = new Uint8Array( |
|
|
atob(encryptedData).split('').map(char => char.charCodeAt(0)) |
|
|
); |
|
|
|
|
|
const iv = dataBuffer.slice(0, 12); |
|
|
const encrypted = dataBuffer.slice(12); |
|
|
|
|
|
const decryptedBuffer = await crypto.subtle.decrypt( |
|
|
{ name: 'AES-GCM', iv }, |
|
|
key, |
|
|
encrypted |
|
|
); |
|
|
|
|
|
const decoder = new TextDecoder(); |
|
|
return decoder.decode(decryptedBuffer); |
|
|
} catch (error) { |
|
|
console.warn('Decryption failed, returning data as-is:', error); |
|
|
return encryptedData; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
export const secureStorage = { |
|
|
async setItem(key: string, value: string): Promise<void> { |
|
|
const encrypted = await encryptData(value); |
|
|
localStorage.setItem(`secure_${key}`, encrypted); |
|
|
}, |
|
|
|
|
|
async getItem(key: string): Promise<string | null> { |
|
|
const encrypted = localStorage.getItem(`secure_${key}`); |
|
|
if (!encrypted) return null; |
|
|
|
|
|
return await decryptData(encrypted); |
|
|
}, |
|
|
|
|
|
removeItem(key: string): void { |
|
|
localStorage.removeItem(`secure_${key}`); |
|
|
} |
|
|
}; |