diff --git a/web/database/utils/worker-crypto-utils.js b/web/crypto/aes-gcm-crypto-utils.js rename from web/database/utils/worker-crypto-utils.js rename to web/crypto/aes-gcm-crypto-utils.js --- a/web/database/utils/worker-crypto-utils.js +++ b/web/crypto/aes-gcm-crypto-utils.js @@ -11,7 +11,7 @@ +ciphertext: Uint8Array, }; -function generateDatabaseCryptoKey({ +function generateCryptoKey({ extractable, }: { +extractable: boolean, @@ -30,7 +30,7 @@ return crypto.getRandomValues(new Uint8Array(12)); } -async function encryptDatabaseFile( +async function encryptData( data: Uint8Array, key: CryptoKey, ): Promise { @@ -49,7 +49,7 @@ }; } -async function decryptDatabaseFile( +async function decryptData( encryptedData: EncryptedData, key: CryptoKey, ): Promise { @@ -84,9 +84,9 @@ } export { - generateDatabaseCryptoKey, - encryptDatabaseFile, - decryptDatabaseFile, + generateCryptoKey, + encryptData, + decryptData, exportKeyToJWK, importJWKKey, }; diff --git a/web/database/database-module-provider.js b/web/database/database-module-provider.js --- a/web/database/database-module-provider.js +++ b/web/database/database-module-provider.js @@ -9,11 +9,11 @@ SQLITE_ENCRYPTION_KEY, } from './utils/constants.js'; import { isDesktopSafari, isSQLiteSupported } from './utils/db-utils.js'; +import WorkerConnectionProxy from './utils/WorkerConnectionProxy.js'; import { exportKeyToJWK, - generateDatabaseCryptoKey, -} from './utils/worker-crypto-utils.js'; -import WorkerConnectionProxy from './utils/WorkerConnectionProxy.js'; + generateCryptoKey, +} from '../crypto/aes-gcm-crypto-utils.js'; import { workerRequestMessageTypes, type WorkerRequestMessage, @@ -135,7 +135,7 @@ if (encryptionKey) { return await exportKeyToJWK(encryptionKey); } - const newEncryptionKey = await generateDatabaseCryptoKey({ + const newEncryptionKey = await generateCryptoKey({ extractable: true, }); await localforage.setItem(SQLITE_ENCRYPTION_KEY, newEncryptionKey); diff --git a/web/database/utils/worker-crypto-utils.test.js b/web/database/utils/worker-crypto-utils.test.js --- a/web/database/utils/worker-crypto-utils.test.js +++ b/web/database/utils/worker-crypto-utils.test.js @@ -2,12 +2,12 @@ import { exportDatabaseContent, importDatabaseContent } from './db-utils.js'; import { - decryptDatabaseFile, - encryptDatabaseFile, + decryptData, + encryptData, exportKeyToJWK, - generateDatabaseCryptoKey, + generateCryptoKey, importJWKKey, -} from './worker-crypto-utils.js'; +} from '../../crypto/aes-gcm-crypto-utils.js'; import { getDatabaseModule } from '../db-module.js'; const TAG_LENGTH = 16; @@ -27,49 +27,47 @@ sqliteQueryExecutor = new dbModule.SQLiteQueryExecutor('test.sqlite'); sqliteQueryExecutor.setMetadata(TEST_KEY, TEST_VAL); - cryptoKey = await generateDatabaseCryptoKey({ extractable: false }); + cryptoKey = await generateCryptoKey({ extractable: false }); }); it('should encrypt database content', async () => { const dbContent: Uint8Array = exportDatabaseContent(dbModule, FILE_PATH); - const { ciphertext, iv } = await encryptDatabaseFile(dbContent, cryptoKey); + const { ciphertext, iv } = await encryptData(dbContent, cryptoKey); expect(iv.byteLength).toBe(IV_LENGTH); expect(ciphertext.length).toBe(dbContent.length + TAG_LENGTH); }); it('is decryptable', async () => { const dbContent: Uint8Array = exportDatabaseContent(dbModule, FILE_PATH); - const encryptedData = await encryptDatabaseFile(dbContent, cryptoKey); - const decrypted = await decryptDatabaseFile(encryptedData, cryptoKey); + const encryptedData = await encryptData(dbContent, cryptoKey); + const decrypted = await decryptData(encryptedData, cryptoKey); expect(decrypted).toEqual(dbContent); }); it('should fail with wrong key', async () => { const dbContent: Uint8Array = exportDatabaseContent(dbModule, FILE_PATH); - const encryptedData = await encryptDatabaseFile(dbContent, cryptoKey); + const encryptedData = await encryptData(dbContent, cryptoKey); - const newCryptoKey = await generateDatabaseCryptoKey({ + const newCryptoKey = await generateCryptoKey({ extractable: false, }); - expect(decryptDatabaseFile(encryptedData, newCryptoKey)).rejects.toThrow(); + expect(decryptData(encryptedData, newCryptoKey)).rejects.toThrow(); }); it('should fail with wrong content', async () => { const dbContent: Uint8Array = exportDatabaseContent(dbModule, FILE_PATH); - const encryptedData = await encryptDatabaseFile(dbContent, cryptoKey); + const encryptedData = await encryptData(dbContent, cryptoKey); const randomizedEncryptedData = { ...encryptedData, ciphertext: encryptedData.ciphertext.map(uint => uint ^ 1), }; - expect( - decryptDatabaseFile(randomizedEncryptedData, cryptoKey), - ).rejects.toThrow(); + expect(decryptData(randomizedEncryptedData, cryptoKey)).rejects.toThrow(); }); it('should create database with decrypted content', async () => { const dbContent: Uint8Array = exportDatabaseContent(dbModule, FILE_PATH); - const encryptedData = await encryptDatabaseFile(dbContent, cryptoKey); - const decrypted = await decryptDatabaseFile(encryptedData, cryptoKey); + const encryptedData = await encryptData(dbContent, cryptoKey); + const decrypted = await decryptData(encryptedData, cryptoKey); importDatabaseContent(decrypted, dbModule, 'new-file.sqlite'); @@ -80,18 +78,18 @@ it('should export and import key in JWK format', async () => { // creating new key - const key = await generateDatabaseCryptoKey({ extractable: true }); + const key = await generateCryptoKey({ extractable: true }); const dbContent: Uint8Array = dbModule.FS.readFile(FILE_PATH, { encoding: 'binary', }); - const encryptedData = await encryptDatabaseFile(dbContent, key); + const encryptedData = await encryptData(dbContent, key); // exporting and importing key const exportedKey = await exportKeyToJWK(key); const importedKey = await importJWKKey(exportedKey); // decrypt using re-created on import key - const decrypted = await decryptDatabaseFile(encryptedData, importedKey); + const decrypted = await decryptData(encryptedData, importedKey); importDatabaseContent(decrypted, dbModule, 'new-file.sqlite'); diff --git a/web/database/worker/db-worker.js b/web/database/worker/db-worker.js --- a/web/database/worker/db-worker.js +++ b/web/database/worker/db-worker.js @@ -6,6 +6,12 @@ getClientStore, processDBStoreOperations, } from './process-operations.js'; +import { + decryptData, + encryptData, + generateCryptoKey, + importJWKKey, +} from '../../crypto/aes-gcm-crypto-utils.js'; import { type SharedWorkerMessageEvent, type WorkerRequestMessage, @@ -30,12 +36,6 @@ exportDatabaseContent, importDatabaseContent, } from '../utils/db-utils.js'; -import { - decryptDatabaseFile, - encryptDatabaseFile, - generateDatabaseCryptoKey, - importJWKKey, -} from '../utils/worker-crypto-utils.js'; localforage.config(localforageConfig); @@ -69,7 +69,7 @@ } else { encryptionKey = await localforage.getItem(SQLITE_ENCRYPTION_KEY); if (!encryptionKey) { - const cryptoKey = await generateDatabaseCryptoKey({ extractable: false }); + const cryptoKey = await generateCryptoKey({ extractable: false }); await localforage.setItem(SQLITE_ENCRYPTION_KEY, cryptoKey); } } @@ -79,7 +79,7 @@ let dbContent = null; try { if (encryptionKey && encryptedContent) { - dbContent = await decryptDatabaseFile(encryptedContent, encryptionKey); + dbContent = await decryptData(encryptedContent, encryptionKey); } } catch (e) { console.error('Error while decrypting content, clearing database content'); @@ -118,7 +118,7 @@ persistInProgress = false; throw new Error('Encryption key is missing'); } - const encryptedData = await encryptDatabaseFile(dbData, encryptionKey); + const encryptedData = await encryptData(dbData, encryptionKey); await localforage.setItem(SQLITE_CONTENT, encryptedData); } persistInProgress = false; @@ -136,7 +136,7 @@ } else if ( message.type === workerRequestMessageTypes.GENERATE_DATABASE_ENCRYPTION_KEY ) { - const cryptoKey = await generateDatabaseCryptoKey({ extractable: false }); + const cryptoKey = await generateCryptoKey({ extractable: false }); await localforage.setItem(SQLITE_ENCRYPTION_KEY, cryptoKey); return undefined; }