'use strict'; // --------------------------------------------------------------------------- // E2E Encryption utilities (Web Crypto API) // --------------------------------------------------------------------------- let _encKey = null; function bytesToBase64(buf) { const bytes = new Uint8Array(buf); let str = ''; for (const b of bytes) str += String.fromCharCode(b); return btoa(str); } function base64ToBytes(b64) { const str = atob(b64); const buf = new Uint8Array(str.length); for (let i = 0; i < str.length; i++) buf[i] = str.charCodeAt(i); return buf; } function bytesToHex(buf) { return Array.from(new Uint8Array(buf)).map(b => b.toString(16).padStart(2, '0')).join(''); } function hexToBytes(hex) { const arr = new Uint8Array(hex.length / 2); for (let i = 0; i < arr.length; i++) arr[i] = parseInt(hex.slice(i*2, i*2+2), 16); return arr; } async function getOrCreateEncKey() { if (_encKey) return _encKey; const storageKey = `diora_enc_key_${window.USER_ID || 0}`; const stored = localStorage.getItem(storageKey); if (stored) { try { const raw = base64ToBytes(stored); _encKey = await crypto.subtle.importKey('raw', raw, {name: 'AES-GCM'}, false, ['encrypt', 'decrypt']); return _encKey; } catch (e) { /* fall through, generate new */ } } // Just signed in with the password: the login form left the derived key (99-init.js // moves it too, but this may run first). const pending = window.USER_ID ? localStorage.getItem('diora_pending_enc_key') : null; if (pending) { localStorage.setItem(storageKey, pending); localStorage.removeItem('diora_pending_enc_key'); return getOrCreateEncKey(); } // No key on this device (signed in through Authelia, or the storage was cleared): never // make a new one — that would lock the existing books away. Ask for the real one // (33-unlock.js); throws DioraLocked when the user puts it off. const raw = await dioraUnlock(); _encKey = await crypto.subtle.importKey('raw', raw, {name: 'AES-GCM'}, false, ['encrypt', 'decrypt']); return _encKey; } async function encryptBytes(key, plainBytes) { const iv = crypto.getRandomValues(new Uint8Array(12)); const ct = await crypto.subtle.encrypt({name: 'AES-GCM', iv}, key, plainBytes); return {iv: bytesToHex(iv), ciphertext: bytesToBase64(ct)}; } async function decryptBytes(key, ivHex, ctB64) { const iv = hexToBytes(ivHex); const ct = base64ToBytes(ctB64); return crypto.subtle.decrypt({name: 'AES-GCM', iv}, key, ct); }