diff --git a/apps/web/package.json b/apps/web/package.json index 8b156e614..e30d17f16 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -19,7 +19,6 @@ "immer": "^8.0.1", "localforage": "^1.7.3", "localforage-getitems": "https://github.com/thecodrr/localForage-getItems.git", - "lzutf8": "^0.5.8", "notes-core": "npm:@streetwriters/notesnook-core@latest", "qclone": "^1.0.4", "react": "^17.0.1", diff --git a/apps/web/public/crypto.worker.js b/apps/web/public/crypto.worker.js index 3413f3d4c..7beb82c84 100644 --- a/apps/web/public/crypto.worker.js +++ b/apps/web/public/crypto.worker.js @@ -130,6 +130,11 @@ const encrypt = (passwordOrKey, plainData) => { if (plainData.type === "plain") { plainData.data = enc.encode(plainData.data); + } else if (plainData.type === "base64") { + plainData.data = sodium.from_base64( + plainData.data, + sodium.base64_variants.ORIGINAL + ); } const { key, salt } = _getKey(passwordOrKey); @@ -152,7 +157,7 @@ const encrypt = (passwordOrKey, plainData) => { return { alg: getAlgorithm( sodium.base64_variants.URLSAFE_NO_PADDING, - plainData.type === "uint8array" ? 1 : 0 // TODO: Crude but works (change this to a more exact boolean flag) + plainData.compress ? 1 : 0 // TODO: Crude but works (change this to a more exact boolean flag) ), cipher, iv, @@ -235,5 +240,5 @@ const webCryptoPolyfill = (seed, sodium) => { function getAlgorithm(base64Variant, compress) { //Template: encryptionAlgorithm-kdfAlgorithm-compressionFlag-base64variant - return `xcha-argon2i13-${compress}-${base64Variant}`; + return `xcha-argon2i13-${compress}-none-${base64Variant}`; } diff --git a/apps/web/src/interfaces/crypto.js b/apps/web/src/interfaces/crypto.js index 2442f223a..9acea6206 100644 --- a/apps/web/src/interfaces/crypto.js +++ b/apps/web/src/interfaces/crypto.js @@ -1,123 +1,3 @@ -/** - * @return {Promise} - */ -function compress(data) { - return loadLZUTF8((lzutf8) => { - lzutf8.compress(data, { - blockSize: 64 * 1024 * 1024, - inputEncoding: "String", - outputEncoding: "ByteArray", - }); - }); -} - -/** - * @return {Promise} - */ -function decompress(data) { - loadLZUTF8((lzutf8) => { - lzutf8.decompress(data, { - blockSize: 64 * 1024 * 1024, - inputEncoding: "ByteArray", - outputEncoding: "String", - }); - }); -} - -function loadLZUTF8(action) { - return new Promise(async (resolve) => { - const lzutf8 = await import("lzutf8"); - resolve(await action(lzutf8)); - }); -} -class Crypto { - isReady = false; - constructor() { - this.sodium = undefined; - } - async _initialize() { - if (this.isReady) return; - return new Promise(async (resolve) => { - window.sodium = { - onload: (_sodium) => { - if (this.isReady) return; - this.isReady = true; - this.sodium = _sodium; - loadScript("/crypto.worker.js").then(resolve); - }, - }; - await loadScript("sodium.js"); - }); - } - - /** - * @private - * @param {{password: string}|{key:string, salt: string}} passwordOrKey - password or derived key - * @param {string} plainData - the plaintext data - */ - _encryptCompressed = async (passwordOrKey, plainData) => { - await this._initialize(); - return global.ncrypto.encrypt.call(this, passwordOrKey, { - type: "uint8array", - data: await compress(plainData), - }); - }; - - /** - * - * @param {{password: string}|{key:string, salt: string}} passwordOrKey - password or derived key - * @param {string} plainData - the plaintext data - * @param {boolean} - */ - encrypt = async (passwordOrKey, plainData, compress) => { - if (compress) - return await this._encryptCompressed(passwordOrKey, plainData); - - await this._initialize(); - return global.ncrypto.encrypt.call(this, passwordOrKey, { - type: "plain", - data: plainData, - }); - }; - - /** - * - * @param {{password: string}|{key:string, salt: string}} passwordOrKey - password or derived key - * @param {{alg: string, salt: string, iv: string, cipher: string}} cipherData - the cipher data - */ - decrypt = async (passwordOrKey, cipherData) => { - const algorithm = parseAlgorithm(cipherData.alg); - if (algorithm.isCompressed) - return await this._decryptCompressed(passwordOrKey, cipherData); - - await this._initialize(); - cipherData.output = "text"; - return global.ncrypto.decrypt.call(this, passwordOrKey, cipherData); - }; - - /** - * @private - * @param {{password: string}|{key:string, salt: string}} passwordOrKey - password or derived key - * @param {{salt: string, iv: string, cipher: string}} cipherData - the cipher data - */ - _decryptCompressed = async (passwordOrKey, cipherData) => { - cipherData.output = "uint8array"; - return await decompress( - await global.ncrypto.decrypt.call(this, passwordOrKey, cipherData) - ); - }; - - deriveKey = async (password, salt, exportKey = false) => { - await this._initialize(); - return global.ncrypto.deriveKey.call(this, password, salt, exportKey); - }; - - hashPassword = async (password, userId) => { - await this._initialize(); - return global.ncrypto.hashPassword.call(this, password, userId); - }; -} - class CryptoWorker { constructor() { this.isReady = false; @@ -179,29 +59,13 @@ class CryptoWorker { }); } - /** - * @private - * @param {{password: string}|{key:string, salt: string}} passwordOrKey - password or derived key - * @param {string} data - the plaintext data - */ - _encryptCompressed = async (passwordOrKey, data) => { - const message = { - passwordOrKey, - data: { type: "uint8array", data: await compress(data) }, - }; - return this._communicate("encrypt", message, [message.data.data.buffer]); - }; - /** * * @param {{password: string}|{key:string, salt: string}} passwordOrKey - password or derived key * @param {string} data - the plaintext data * @param {boolean} compress */ - encrypt = (passwordOrKey, data, compress) => { - if (compress) { - return this._encryptCompressed(passwordOrKey, data); - } + encrypt = (passwordOrKey, data) => { return this._communicate("encrypt", { passwordOrKey, data: { type: "plain", data }, @@ -214,29 +78,10 @@ class CryptoWorker { * @param {{alg: string, salt: string, iv: string, cipher: string}} cipherData - the cipher data */ decrypt = (passwordOrKey, cipherData) => { - const algorithm = parseAlgorithm(cipherData.alg); - if (algorithm.isCompressed) - return this._decryptCompressed(passwordOrKey, cipherData); - cipherData.output = "text"; return this._communicate("decrypt", { passwordOrKey, cipher: cipherData }); }; - /** - * @private - * @param {{password: string}|{key:string, salt: string}} passwordOrKey - password or derived key - * @param {{alg: string, salt: string, iv: string, cipher: string}} cipherData - the cipher data - */ - _decryptCompressed = async (passwordOrKey, cipherData) => { - cipherData.output = "uint8array"; - return await decompress( - await this._communicate("decrypt", { - passwordOrKey, - cipher: cipherData, - }) - ); - }; - deriveKey = (password, salt, exportKey = false) => { return this._communicate("deriveKey", { password, salt, exportKey }); }; @@ -274,11 +119,68 @@ function loadScript(url) { */ function parseAlgorithm(alg) { if (!alg) return {}; - const [enc, kdf, compressed, base64variant] = alg.split("-"); + const [enc, kdf, compressed, compressionAlg, base64variant] = alg.split("-"); return { encryptionAlgorithm: enc, kdfAlgorithm: kdf, isCompressed: compressed === "1", + compressionAlgorithm: compressionAlg, base64_variant: base64variant, }; } + +class Crypto { + isReady = false; + constructor() { + this.sodium = undefined; + } + async _initialize() { + if (this.isReady) return; + return new Promise(async (resolve) => { + window.sodium = { + onload: (_sodium) => { + if (this.isReady) return; + this.isReady = true; + this.sodium = _sodium; + loadScript("/crypto.worker.js").then(resolve); + }, + }; + await loadScript("sodium.js"); + }); + } + + /** + * + * @param {{password: string}|{key:string, salt: string}} passwordOrKey - password or derived key + * @param {string} plainData - the plaintext data + * @param {boolean} + */ + encrypt = async (passwordOrKey, plainData) => { + await this._initialize(); + return global.ncrypto.encrypt.call(this, passwordOrKey, { + type: "plain", + data: plainData, + }); + }; + + /** + * + * @param {{password: string}|{key:string, salt: string}} passwordOrKey - password or derived key + * @param {{alg: string, salt: string, iv: string, cipher: string}} cipherData - the cipher data + */ + decrypt = async (passwordOrKey, cipherData) => { + await this._initialize(); + cipherData.output = "text"; + return global.ncrypto.decrypt.call(this, passwordOrKey, cipherData); + }; + + deriveKey = async (password, salt, exportKey = false) => { + await this._initialize(); + return global.ncrypto.deriveKey.call(this, password, salt, exportKey); + }; + + hashPassword = async (password, userId) => { + await this._initialize(); + return global.ncrypto.hashPassword.call(this, password, userId); + }; +} diff --git a/apps/web/yarn.lock b/apps/web/yarn.lock index 24b95262a..335c03f13 100644 --- a/apps/web/yarn.lock +++ b/apps/web/yarn.lock @@ -7854,13 +7854,6 @@ lru-cache@^6.0.0: dependencies: yallist "^4.0.0" -lzutf8@^0.5.8: - version "0.5.8" - resolved "https://registry.yarnpkg.com/lzutf8/-/lzutf8-0.5.8.tgz#e07c531ad086258c0d842646bd3b973565823330" - integrity sha512-YrwDpLWfVFJfVfVErafPsglTpil+9BIfume6UJ6EWOFFwTJcyvMvDEFts3Ec8rEo4x1hkbh5cDnTAREfzdTSgA== - dependencies: - readable-stream "^3.6.0" - magic-string@^0.25.0, magic-string@^0.25.7: version "0.25.7" resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.7.tgz#3f497d6fd34c669c6798dcb821f2ef31f5445051" @@ -8374,7 +8367,7 @@ normalize-url@^3.0.0: "notes-core@git+ssh://git@github.com:streetwriters/notesnook-core.git": version "6.4.0" - resolved "git+ssh://git@github.com:streetwriters/notesnook-core.git#d655fae4986861c0ddea559613a448554c046970" + resolved "git+ssh://git@github.com:streetwriters/notesnook-core.git#60bd949007d75650b89d94259f8464402accd0e1" dependencies: fast-sort "^2.0.1" fuzzysearch "^1.0.3"