mirror of
https://github.com/streetwriters/notesnook.git
synced 2026-02-24 12:12:54 +01:00
feat: add WebCrypto polyfill for browsers that don't have it in WebWorkers
This commit is contained in:
@@ -1,32 +1,50 @@
|
||||
/* eslint-disable */
|
||||
|
||||
var context;
|
||||
if (!self instanceof Window) {
|
||||
self.sodium = {
|
||||
onload: function (_sodium) {
|
||||
context = { sodium: _sodium };
|
||||
sendMessage("loaded");
|
||||
},
|
||||
};
|
||||
importScripts("sodium.js");
|
||||
|
||||
if (!self.document) {
|
||||
self.addEventListener("message", onMessage);
|
||||
}
|
||||
|
||||
function onMessage(ev) {
|
||||
const { type, data, messageId } = ev.data;
|
||||
if (type === "encrypt") {
|
||||
const { passwordOrKey, data: _data } = data;
|
||||
const cipher = encrypt.call(context, passwordOrKey, _data);
|
||||
sendMessage("encrypt", cipher, messageId);
|
||||
} else if (type === "decrypt") {
|
||||
const { passwordOrKey, cipher } = data;
|
||||
const plainText = decrypt.call(context, passwordOrKey, cipher);
|
||||
sendMessage("decrypt", plainText, messageId);
|
||||
} else if (type === "deriveKey") {
|
||||
const { password, salt, exportKey } = data;
|
||||
const derivedKey = deriveKey.call(context, password, salt, exportKey);
|
||||
sendMessage("deriveKey", derivedKey, messageId);
|
||||
try {
|
||||
switch (type) {
|
||||
case "encrypt": {
|
||||
const { passwordOrKey, data: _data } = data;
|
||||
const cipher = encrypt.call(context, passwordOrKey, _data);
|
||||
sendMessage("encrypt", cipher, messageId);
|
||||
break;
|
||||
}
|
||||
case "decrypt": {
|
||||
const { passwordOrKey, cipher } = data;
|
||||
const plainText = decrypt.call(context, passwordOrKey, cipher);
|
||||
sendMessage("decrypt", plainText, messageId);
|
||||
break;
|
||||
}
|
||||
case "deriveKey": {
|
||||
const { password, salt, exportKey } = data;
|
||||
const derivedKey = deriveKey.call(context, password, salt, exportKey);
|
||||
sendMessage("deriveKey", derivedKey, messageId);
|
||||
break;
|
||||
}
|
||||
case "load": {
|
||||
const { seed } = data;
|
||||
self.sodium = {
|
||||
onload: function (_sodium) {
|
||||
context = { sodium: _sodium };
|
||||
// create the crypto polyfill if necessary
|
||||
webCryptoPolyfill(seed, _sodium);
|
||||
sendMessage("load", {}, messageId);
|
||||
},
|
||||
};
|
||||
importScripts("sodium.js");
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
sendMessage(type, { error }, messageId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,10 +146,52 @@ const decrypt = (passwordOrKey, { iv, cipher, salt }) => {
|
||||
return plainText;
|
||||
};
|
||||
|
||||
if (self instanceof Window) {
|
||||
if (self.document) {
|
||||
self.ncrypto = {
|
||||
decrypt,
|
||||
deriveKey,
|
||||
encrypt,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* If not available natively, uses a separate CSPRNG to polyfill the Web Crypto API.
|
||||
* Used in worker threads in some browsers.
|
||||
* @param seed Securely generated 32-byte key.
|
||||
*/
|
||||
const webCryptoPolyfill = (seed, sodium) => {
|
||||
if ("getRandomValues" in crypto) return;
|
||||
|
||||
const nonce = new Uint32Array(2);
|
||||
crypto = {
|
||||
getRandomValues: (array) => {
|
||||
if (!array) {
|
||||
throw new TypeError(
|
||||
`Failed to execute 'getRandomValues' on 'Crypto': ${
|
||||
array === null
|
||||
? "parameter 1 is not of type 'ArrayBufferView'"
|
||||
: "1 argument required, but only 0 present"
|
||||
}.`
|
||||
);
|
||||
}
|
||||
/* Handle circular dependency between this polyfill and libsodium */
|
||||
const sodiumExists = typeof sodium.crypto_stream_chacha20 === "function";
|
||||
if (!sodiumExists) {
|
||||
throw new Error("No CSPRNG found.");
|
||||
}
|
||||
++nonce[nonce[0] === 4294967295 ? 1 : 0];
|
||||
const newBytes = sodium().crypto_stream_chacha20(
|
||||
array.byteLength,
|
||||
seed,
|
||||
new Uint8Array(nonce.buffer, nonce.byteOffset, nonce.byteLength)
|
||||
);
|
||||
new Uint8Array(array.buffer, array.byteOffset, array.byteLength).set(
|
||||
newBytes
|
||||
);
|
||||
sodium().memzero(newBytes);
|
||||
return array;
|
||||
},
|
||||
subtle: {},
|
||||
};
|
||||
self.crypto = crypto;
|
||||
};
|
||||
|
||||
@@ -50,36 +50,37 @@ class CryptoWorker {
|
||||
}
|
||||
async _initialize() {
|
||||
if (this.isReady) return;
|
||||
return new Promise((resolve) => {
|
||||
this.worker = new Worker("crypto.worker.js");
|
||||
this.worker.onmessage = (ev) => {
|
||||
const { type } = ev.data;
|
||||
if (type === "loaded") {
|
||||
this.worker.onmessage = undefined;
|
||||
this.isReady = true;
|
||||
resolve(true);
|
||||
}
|
||||
};
|
||||
});
|
||||
this.worker = new Worker("crypto.worker.js");
|
||||
const buffer = Buffer.allocUnsafe(32);
|
||||
crypto.getRandomValues(buffer);
|
||||
const message = { seed: buffer.buffer };
|
||||
await this._communicate("load", message, [message.seed], false);
|
||||
this.isReady = true;
|
||||
}
|
||||
|
||||
_communicate(type, data) {
|
||||
return new Promise(async (resolve) => {
|
||||
await this._initialize();
|
||||
_communicate(type, data, transferables = [], init = true) {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
if (init) await this._initialize();
|
||||
const messageId = Math.random().toString(36).substr(2, 9);
|
||||
const onMessage = (e) => {
|
||||
const { type: _type, messageId: _mId } = e.data;
|
||||
const { type: _type, messageId: _mId, data } = e.data;
|
||||
if (_type === type && _mId === messageId) {
|
||||
this.worker.removeEventListener("message", onMessage);
|
||||
resolve(e.data.data);
|
||||
if (data.error) {
|
||||
return reject(data.error);
|
||||
}
|
||||
resolve(data);
|
||||
}
|
||||
};
|
||||
this.worker.addEventListener("message", onMessage);
|
||||
this.worker.postMessage({
|
||||
type,
|
||||
data,
|
||||
messageId,
|
||||
});
|
||||
this.worker.postMessage(
|
||||
{
|
||||
type,
|
||||
data,
|
||||
messageId,
|
||||
},
|
||||
transferables
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user