mirror of
https://github.com/streetwriters/notesnook.git
synced 2025-12-16 19:57:52 +01:00
crypto: remove crypto-worker & simplify crypto interface
This commit is contained in:
committed by
Abdullah Atta
parent
6554f900d7
commit
fac788d2a9
1426
apps/web/package-lock.json
generated
1426
apps/web/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -20,7 +20,6 @@
|
||||
"@notesnook/common": "file:../../packages/common",
|
||||
"@notesnook/core": "file:../../packages/core",
|
||||
"@notesnook/crypto": "file:../../packages/crypto",
|
||||
"@notesnook/crypto-worker": "file:../../packages/crypto-worker",
|
||||
"@notesnook/desktop": "file:../desktop",
|
||||
"@notesnook/editor": "file:../../packages/editor",
|
||||
"@notesnook/logger": "file:../../packages/logger",
|
||||
|
||||
@@ -22,7 +22,7 @@ import { xxhash64, createXXHash64 } from "hash-wasm";
|
||||
import axios, { AxiosProgressEvent } from "axios";
|
||||
import { AppEventManager, AppEvents } from "../common/app-events";
|
||||
import { StreamableFS } from "@notesnook/streamable-fs";
|
||||
import { getNNCrypto } from "./nncrypto.stub";
|
||||
import { NNCrypto } from "./nncrypto";
|
||||
import hosts from "@notesnook/core/dist/utils/constants";
|
||||
import { sendAttachmentsProgressEvent } from "@notesnook/core/dist/common";
|
||||
import { saveAs } from "file-saver";
|
||||
@@ -34,7 +34,7 @@ import { ProgressStream } from "../utils/streams/progress-stream";
|
||||
import { consumeReadableStream } from "../utils/stream";
|
||||
import { Base64DecoderStream } from "../utils/streams/base64-decoder-stream";
|
||||
import { toBlob } from "@notesnook-importer/core/dist/src/utils/stream";
|
||||
import { Cipher, OutputFormat, SerializedKey } from "@notesnook/crypto";
|
||||
import { Cipher, DataFormat, SerializedKey } from "@notesnook/crypto";
|
||||
import { IDataType } from "hash-wasm/dist/lib/util";
|
||||
import { IndexedDBKVStore } from "./key-value";
|
||||
import FileHandle from "@notesnook/streamable-fs/dist/src/filehandle";
|
||||
@@ -53,8 +53,6 @@ async function writeEncryptedFile(
|
||||
key: SerializedKey,
|
||||
hash: string
|
||||
) {
|
||||
const crypto = await getNNCrypto();
|
||||
|
||||
if (!IndexedDBKVStore.isIndexedDBSupported())
|
||||
throw new Error("This browser does not support IndexedDB.");
|
||||
|
||||
@@ -65,7 +63,7 @@ async function writeEncryptedFile(
|
||||
const fileHandle = await streamablefs.createFile(hash, file.size, file.type);
|
||||
sendAttachmentsProgressEvent("encrypt", hash, 1, 0);
|
||||
|
||||
const { iv, stream } = await crypto.createEncryptionStream(key);
|
||||
const { iv, stream } = await NNCrypto.createEncryptionStream(key);
|
||||
await file
|
||||
.stream()
|
||||
.pipeThrough(new ChunkedStream(CHUNK_SIZE))
|
||||
@@ -84,7 +82,7 @@ async function writeEncryptedFile(
|
||||
)
|
||||
.pipeTo(fileHandle.writeable);
|
||||
|
||||
sendAttachmentsProgressEvent("encrypt", hash, 1);
|
||||
sendAttachmentsProgressEvent("encrypt", hash, 1, 1);
|
||||
|
||||
return {
|
||||
chunkSize: CHUNK_SIZE,
|
||||
@@ -155,15 +153,14 @@ async function hashStream(reader: ReadableStreamDefaultReader<Uint8Array>) {
|
||||
async function readEncrypted(
|
||||
filename: string,
|
||||
key: SerializedKey,
|
||||
cipherData: Cipher & { outputType: OutputFormat }
|
||||
cipherData: Cipher<DataFormat> & { outputType: DataFormat }
|
||||
) {
|
||||
const fileHandle = await streamablefs.readFile(filename);
|
||||
if (!fileHandle) {
|
||||
console.error(`File not found. (File hash: ${filename})`);
|
||||
return null;
|
||||
}
|
||||
const crypto = await getNNCrypto();
|
||||
const decryptionStream = await crypto.createDecryptionStream(
|
||||
const decryptionStream = await NNCrypto.createDecryptionStream(
|
||||
key,
|
||||
cipherData.iv
|
||||
);
|
||||
@@ -527,8 +524,7 @@ export async function decryptFile(
|
||||
|
||||
const { key, iv } = fileMetadata;
|
||||
|
||||
const crypto = await getNNCrypto();
|
||||
const decryptionStream = await crypto.createDecryptionStream(key, iv);
|
||||
const decryptionStream = await NNCrypto.createDecryptionStream(key, iv);
|
||||
return await toBlob(fileHandle.readable.pipeThrough(decryptionStream));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
/*
|
||||
This file is part of the Notesnook project (https://notesnook.com/)
|
||||
|
||||
Copyright (C) 2023 Streetwriters (Private) Limited
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { INNCrypto } from "@notesnook/crypto/dist/src/interfaces";
|
||||
import CryptoWorker from "@notesnook/crypto-worker/dist/src/worker.js?worker";
|
||||
|
||||
async function loadNNCrypto() {
|
||||
const hasWorker = "Worker" in window || "Worker" in global;
|
||||
// if (IS_DESKTOP_APP && window.NativeNNCrypto) {
|
||||
// return window.NativeNNCrypto;
|
||||
// } else
|
||||
if (hasWorker) {
|
||||
const { NNCryptoWorker } = await import("@notesnook/crypto-worker");
|
||||
return NNCryptoWorker;
|
||||
} else {
|
||||
const { NNCrypto } = await import("@notesnook/crypto");
|
||||
return NNCrypto;
|
||||
}
|
||||
}
|
||||
|
||||
let instance: INNCrypto | null = null;
|
||||
|
||||
export function getNNCrypto(): Promise<INNCrypto> {
|
||||
if (instance) return Promise.resolve(instance);
|
||||
return queueify<INNCrypto>(async () => {
|
||||
const NNCrypto = await loadNNCrypto();
|
||||
instance = new NNCrypto(new CryptoWorker());
|
||||
return instance;
|
||||
});
|
||||
}
|
||||
|
||||
let processing = false;
|
||||
type PromiseResolve = <T>(value: Awaited<T>) => void;
|
||||
const queue: Array<PromiseResolve> = [];
|
||||
async function queueify<T>(action: () => Promise<T>): Promise<T> {
|
||||
if (processing)
|
||||
return new Promise((resolve) => {
|
||||
queue.push(resolve as PromiseResolve);
|
||||
});
|
||||
|
||||
processing = true;
|
||||
const result = await action();
|
||||
processing = false;
|
||||
|
||||
while (queue.length > 0) {
|
||||
const resolve = queue.pop();
|
||||
if (!resolve) continue;
|
||||
resolve(result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
24
apps/web/src/interfaces/nncrypto.ts
Normal file
24
apps/web/src/interfaces/nncrypto.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
This file is part of the Notesnook project (https://notesnook.com/)
|
||||
|
||||
Copyright (C) 2023 Streetwriters (Private) Limited
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { INNCrypto } from "@notesnook/crypto";
|
||||
import CryptoWorker from "./crypto.worker?worker";
|
||||
import { wrap } from "comlink";
|
||||
|
||||
export const NNCrypto = wrap<INNCrypto>(new CryptoWorker()) as INNCrypto;
|
||||
40
apps/web/src/interfaces/nncrypto.worker.ts
Normal file
40
apps/web/src/interfaces/nncrypto.worker.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
This file is part of the Notesnook project (https://notesnook.com/)
|
||||
|
||||
Copyright (C) 2023 Streetwriters (Private) Limited
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { NNCrypto, Chunk, SerializedKey } from "@notesnook/crypto";
|
||||
import { expose, transfer } from "comlink";
|
||||
|
||||
class NNCryptoWorker extends NNCrypto {
|
||||
override async createDecryptionStream(
|
||||
key: SerializedKey,
|
||||
iv: string
|
||||
): Promise<TransformStream<Uint8Array, Uint8Array>> {
|
||||
const stream = await super.createDecryptionStream(key, iv);
|
||||
return transfer(stream, [stream]);
|
||||
}
|
||||
|
||||
override async createEncryptionStream(
|
||||
key: SerializedKey
|
||||
): Promise<{ iv: string; stream: TransformStream<Chunk, Uint8Array> }> {
|
||||
const result = await super.createEncryptionStream(key);
|
||||
return transfer(result, [result.stream]);
|
||||
}
|
||||
}
|
||||
|
||||
expose(new NNCryptoWorker());
|
||||
@@ -23,7 +23,7 @@ import {
|
||||
MemoryKVStore,
|
||||
IKVStore
|
||||
} from "./key-value";
|
||||
import { getNNCrypto } from "./nncrypto.stub";
|
||||
import { NNCrypto } from "./nncrypto";
|
||||
import type { Cipher, SerializedKey } from "@notesnook/crypto/dist/src/types";
|
||||
|
||||
type EncryptedKey = { iv: Uint8Array; cipher: BufferSource };
|
||||
@@ -82,8 +82,7 @@ export class NNStorage {
|
||||
const { password, salt } = credentials;
|
||||
if (!password) throw new Error("Invalid data provided to deriveCryptoKey.");
|
||||
|
||||
const crypto = await getNNCrypto();
|
||||
const keyData = await crypto.exportKey(password, salt);
|
||||
const keyData = await NNCrypto.exportKey(password, salt);
|
||||
|
||||
if (
|
||||
(await IndexedDBKVStore.isIndexedDBSupported()) &&
|
||||
@@ -124,41 +123,39 @@ export class NNStorage {
|
||||
): Promise<SerializedKey> {
|
||||
if (!password)
|
||||
throw new Error("Invalid data provided to generateCryptoKey.");
|
||||
const crypto = await getNNCrypto();
|
||||
return await crypto.exportKey(password, salt);
|
||||
|
||||
return await NNCrypto.exportKey(password, salt);
|
||||
}
|
||||
|
||||
async hash(password: string, email: string): Promise<string> {
|
||||
const crypto = await getNNCrypto();
|
||||
return await crypto.hash(password, `${APP_SALT}${email}`);
|
||||
return await NNCrypto.hash(password, `${APP_SALT}${email}`);
|
||||
}
|
||||
|
||||
async encrypt(key: SerializedKey, plainText: string): Promise<Cipher> {
|
||||
const crypto = await getNNCrypto();
|
||||
return await crypto.encrypt(
|
||||
key,
|
||||
{ format: "text", data: plainText },
|
||||
"base64"
|
||||
);
|
||||
encrypt(key: SerializedKey, plainText: string): Promise<Cipher<"base64">> {
|
||||
return NNCrypto.encrypt(key, plainText, "text", "base64");
|
||||
}
|
||||
|
||||
async decrypt(
|
||||
encryptMulti(
|
||||
key: SerializedKey,
|
||||
cipherData: Cipher
|
||||
items: string[]
|
||||
): Promise<Cipher<"base64">[]> {
|
||||
return NNCrypto.encryptMulti(key, items, "text", "base64");
|
||||
}
|
||||
|
||||
decrypt(
|
||||
key: SerializedKey,
|
||||
cipherData: Cipher<"base64">
|
||||
): Promise<string | undefined> {
|
||||
const crypto = await getNNCrypto();
|
||||
cipherData.format = "base64";
|
||||
return await crypto.decrypt(key, cipherData, "text");
|
||||
return NNCrypto.decrypt(key, cipherData, "text");
|
||||
}
|
||||
|
||||
async decryptMulti(
|
||||
decryptMulti(
|
||||
key: SerializedKey,
|
||||
items: Cipher[]
|
||||
items: Cipher<"base64">[]
|
||||
): Promise<string[] | undefined> {
|
||||
const crypto = await getNNCrypto();
|
||||
|
||||
items.forEach((c) => (c.format = "base64"));
|
||||
return await crypto.decryptMulti(key, items, "text");
|
||||
return NNCrypto.decryptMulti(key, items, "text");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -226,23 +223,3 @@ async function aesDecrypt(
|
||||
);
|
||||
return dec.decode(plainText);
|
||||
}
|
||||
|
||||
// async function main() {
|
||||
// const nncrypto = await getNNCrypto();
|
||||
// const electronNNCrypto = new NNCryptoElectron();
|
||||
|
||||
// console.time("nncrypto");
|
||||
// for (let i = 0; i < 100; ++i) {
|
||||
// await nncrypto.hash("mypassword", APP_SALT);
|
||||
// }
|
||||
// console.timeEnd("nncrypto");
|
||||
|
||||
// console.time("electron");
|
||||
// for (let i = 0; i < 100; ++i) {
|
||||
// await electronNNCrypto.hash("mypassword", APP_SALT);
|
||||
// }
|
||||
// console.timeEnd("electron");
|
||||
// }
|
||||
|
||||
// main();
|
||||
// setTimeout(main, 10000);
|
||||
|
||||
@@ -1,191 +0,0 @@
|
||||
/*
|
||||
This file is part of the Notesnook project (https://notesnook.com/)
|
||||
|
||||
Copyright (C) 2023 Streetwriters (Private) Limited
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {
|
||||
SerializedKey,
|
||||
Plaintext,
|
||||
OutputFormat,
|
||||
Cipher,
|
||||
EncryptionKey,
|
||||
INNCrypto,
|
||||
Output
|
||||
} from "@notesnook/crypto";
|
||||
import { NNCryptoWorkerModule } from "./src/worker";
|
||||
import { wrap } from "comlink";
|
||||
|
||||
export class NNCryptoWorker implements INNCrypto {
|
||||
private workermodule?: NNCryptoWorkerModule;
|
||||
private isReady = false;
|
||||
|
||||
constructor(private readonly worker?: Worker) {}
|
||||
|
||||
private async init() {
|
||||
if (!this.worker) throw new Error("worker cannot be undefined.");
|
||||
if (this.isReady) return;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
this.workermodule = wrap<NNCryptoWorkerModule>(this.worker);
|
||||
// this.workermodule = await spawn<NNCryptoWorkerModule>(this.worker);
|
||||
this.isReady = true;
|
||||
}
|
||||
|
||||
async encrypt(
|
||||
key: SerializedKey,
|
||||
plaintext: Plaintext<OutputFormat>,
|
||||
outputFormat: OutputFormat = "uint8array"
|
||||
): Promise<Cipher> {
|
||||
await this.init();
|
||||
if (!this.workermodule) throw new Error("Worker module is not ready.");
|
||||
|
||||
return this.workermodule.encrypt(key, plaintext, outputFormat);
|
||||
}
|
||||
|
||||
async decrypt<TOutputFormat extends OutputFormat>(
|
||||
key: SerializedKey,
|
||||
cipherData: Cipher,
|
||||
outputFormat: TOutputFormat = "text" as TOutputFormat
|
||||
): Promise<Output<TOutputFormat>> {
|
||||
await this.init();
|
||||
if (!this.workermodule) throw new Error("Worker module is not ready.");
|
||||
|
||||
return this.workermodule.decrypt(key, cipherData, outputFormat);
|
||||
}
|
||||
|
||||
async decryptMulti<TOutputFormat extends OutputFormat>(
|
||||
key: SerializedKey,
|
||||
items: Cipher[],
|
||||
outputFormat: TOutputFormat = "text" as TOutputFormat
|
||||
): Promise<Output<TOutputFormat>[]> {
|
||||
await this.init();
|
||||
if (!this.workermodule) throw new Error("Worker module is not ready.");
|
||||
|
||||
return this.workermodule.decryptMulti(key, items, outputFormat);
|
||||
}
|
||||
|
||||
async hash(password: string, salt: string): Promise<string> {
|
||||
await this.init();
|
||||
if (!this.workermodule) throw new Error("Worker module is not ready.");
|
||||
|
||||
return this.workermodule.hash(password, salt);
|
||||
}
|
||||
|
||||
async deriveKey(password: string, salt?: string): Promise<EncryptionKey> {
|
||||
await this.init();
|
||||
if (!this.workermodule) throw new Error("Worker module is not ready.");
|
||||
|
||||
return this.workermodule.deriveKey(password, salt);
|
||||
}
|
||||
|
||||
async exportKey(password: string, salt?: string): Promise<SerializedKey> {
|
||||
await this.init();
|
||||
if (!this.workermodule) throw new Error("Worker module is not ready.");
|
||||
|
||||
return this.workermodule.exportKey(password, salt);
|
||||
}
|
||||
|
||||
async createEncryptionStream(key: SerializedKey) {
|
||||
await this.init();
|
||||
if (!this.workermodule) throw new Error("Worker module is not ready.");
|
||||
return this.workermodule.createEncryptionStream(key);
|
||||
}
|
||||
|
||||
async createDecryptionStream(key: SerializedKey, iv: string) {
|
||||
await this.init();
|
||||
if (!this.workermodule) throw new Error("Worker module is not ready.");
|
||||
const { stream } = await this.workermodule.createDecryptionStream(key, iv);
|
||||
return stream;
|
||||
}
|
||||
// async encryptStream(
|
||||
// key: SerializedKey,
|
||||
// stream: IStreamable,
|
||||
// streamId?: string
|
||||
// ): Promise<string> {
|
||||
// if (!streamId) throw new Error("streamId is required.");
|
||||
// await this.init();
|
||||
// if (!this.workermodule) throw new Error("Worker module is not ready.");
|
||||
// if (!this.worker) throw new Error("Worker is not ready.");
|
||||
|
||||
// const eventListener = await this.createWorkerStream(
|
||||
// streamId,
|
||||
// stream,
|
||||
// () => {
|
||||
// if (this.worker)
|
||||
// this.worker.removeEventListener("message", eventListener);
|
||||
// }
|
||||
// );
|
||||
// this.worker.addEventListener("message", eventListener);
|
||||
// const iv = await this.workermodule.createEncryptionStream(streamId, key);
|
||||
// this.worker.removeEventListener("message", eventListener);
|
||||
// return iv;
|
||||
// }
|
||||
|
||||
// async decryptStream(
|
||||
// key: SerializedKey,
|
||||
// iv: string,
|
||||
// stream: IStreamable,
|
||||
// streamId?: string
|
||||
// ): Promise<void> {
|
||||
// if (!streamId) throw new Error("streamId is required.");
|
||||
// await this.init();
|
||||
// if (!this.workermodule) throw new Error("Worker module is not ready.");
|
||||
// if (!this.worker) throw new Error("Worker is not ready.");
|
||||
|
||||
// const eventListener = await this.createWorkerStream(
|
||||
// streamId,
|
||||
// stream,
|
||||
// () => {
|
||||
// if (this.worker)
|
||||
// this.worker.removeEventListener("message", eventListener);
|
||||
// }
|
||||
// );
|
||||
// this.worker.addEventListener("message", eventListener);
|
||||
// await this.workermodule.createDecryptionStream(streamId, iv, key);
|
||||
// this.worker.removeEventListener("message", eventListener);
|
||||
// }
|
||||
|
||||
// private async createWorkerStream(
|
||||
// streamId: string,
|
||||
// stream: IStreamable,
|
||||
// done: () => void
|
||||
// ): Promise<EventListenerObject> {
|
||||
// const readEventType = `${streamId}:read`;
|
||||
// const writeEventType = `${streamId}:write`;
|
||||
// let finished = false;
|
||||
// return {
|
||||
// handleEvent: async (ev: MessageEvent) => {
|
||||
// if (finished) return;
|
||||
|
||||
// const { type } = ev.data;
|
||||
// if (type === readEventType) {
|
||||
// const chunk = await stream.read();
|
||||
// if (!chunk || !this.worker || !chunk.data) return;
|
||||
// this.worker.postMessage({ type, data: chunk }, [chunk.data.buffer]);
|
||||
// } else if (type === writeEventType) {
|
||||
// const chunk = ev.data.data as Chunk;
|
||||
// await stream.write(chunk);
|
||||
// if (chunk.final) {
|
||||
// finished = true;
|
||||
// done();
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// };
|
||||
// }
|
||||
}
|
||||
49
packages/crypto-worker/package-lock.json
generated
49
packages/crypto-worker/package-lock.json
generated
@@ -1,49 +0,0 @@
|
||||
{
|
||||
"name": "@notesnook/crypto-worker",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@notesnook/crypto-worker",
|
||||
"version": "1.0.0",
|
||||
"license": "GPL-3.0-or-later",
|
||||
"dependencies": {
|
||||
"@notesnook/crypto": "file:../crypto",
|
||||
"comlink": "^4.3.1"
|
||||
},
|
||||
"devDependencies": {}
|
||||
},
|
||||
"../crypto": {
|
||||
"name": "@notesnook/crypto",
|
||||
"version": "1.1.1",
|
||||
"license": "GPL-3.0-or-later",
|
||||
"dependencies": {
|
||||
"@notesnook/sodium": "file:../sodium"
|
||||
},
|
||||
"devDependencies": {}
|
||||
},
|
||||
"node_modules/@notesnook/crypto": {
|
||||
"resolved": "../crypto",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/comlink": {
|
||||
"version": "4.3.1",
|
||||
"resolved": "https://registry.npmjs.org/comlink/-/comlink-4.3.1.tgz",
|
||||
"integrity": "sha512-+YbhUdNrpBZggBAHWcgQMLPLH1KDF3wJpeqrCKieWQ8RL7atmgsgTQko1XEBK6PsecfopWNntopJ+ByYG1lRaA=="
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@notesnook/crypto": {
|
||||
"version": "file:../crypto",
|
||||
"requires": {
|
||||
"@notesnook/sodium": "file:../sodium"
|
||||
}
|
||||
},
|
||||
"comlink": {
|
||||
"version": "4.3.1",
|
||||
"resolved": "https://registry.npmjs.org/comlink/-/comlink-4.3.1.tgz",
|
||||
"integrity": "sha512-+YbhUdNrpBZggBAHWcgQMLPLH1KDF3wJpeqrCKieWQ8RL7atmgsgTQko1XEBK6PsecfopWNntopJ+ByYG1lRaA=="
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"name": "@notesnook/crypto-worker",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
"scripts": {
|
||||
"build": "tsc --declaration --outDir ./dist"
|
||||
},
|
||||
"author": "",
|
||||
"license": "GPL-3.0-or-later",
|
||||
"dependencies": {
|
||||
"@notesnook/crypto": "file:../crypto",
|
||||
"comlink": "^4.3.1"
|
||||
},
|
||||
"devDependencies": {}
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
/*
|
||||
This file is part of the Notesnook project (https://notesnook.com/)
|
||||
|
||||
Copyright (C) 2023 Streetwriters (Private) Limited
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {
|
||||
Cipher,
|
||||
OutputFormat,
|
||||
Plaintext,
|
||||
SerializedKey
|
||||
} from "@notesnook/crypto/dist/src/types";
|
||||
import { expose, transfer } from "comlink";
|
||||
import type { Decryption, NNCrypto, Output } from "@notesnook/crypto";
|
||||
|
||||
let crypto: NNCrypto | null = null;
|
||||
let decryption: typeof Decryption | null = null;
|
||||
|
||||
async function loadNNCrypto(): Promise<NNCrypto> {
|
||||
if (crypto) return crypto;
|
||||
const { NNCrypto } = await import("@notesnook/crypto");
|
||||
return (crypto = new NNCrypto());
|
||||
}
|
||||
|
||||
async function loadDecryptionModule(): Promise<typeof Decryption> {
|
||||
if (decryption) return decryption;
|
||||
const { Decryption } = await import("@notesnook/crypto");
|
||||
return (decryption = Decryption);
|
||||
}
|
||||
|
||||
const module = {
|
||||
exportKey: async function (password: string, salt?: string) {
|
||||
const crypto = await loadNNCrypto();
|
||||
return crypto.exportKey(password, salt);
|
||||
},
|
||||
deriveKey: async function (password: string, salt?: string) {
|
||||
const crypto = await loadNNCrypto();
|
||||
return crypto.deriveKey(password, salt);
|
||||
},
|
||||
hash: async function (password: string, salt: string) {
|
||||
const crypto = await loadNNCrypto();
|
||||
return crypto.hash(password, salt);
|
||||
},
|
||||
encrypt: async function (
|
||||
key: SerializedKey,
|
||||
plaintext: Plaintext<OutputFormat>,
|
||||
outputFormat?: OutputFormat
|
||||
) {
|
||||
const crypto = await loadNNCrypto();
|
||||
return crypto.encrypt(key, plaintext, outputFormat);
|
||||
},
|
||||
decrypt: async function <TOutputFormat extends OutputFormat>(
|
||||
key: SerializedKey,
|
||||
cipherData: Cipher,
|
||||
outputFormat: TOutputFormat = "text" as TOutputFormat
|
||||
) {
|
||||
const decryption = await loadDecryptionModule();
|
||||
return decryption.decrypt(key, cipherData, outputFormat);
|
||||
},
|
||||
decryptMulti: async function <TOutputFormat extends OutputFormat>(
|
||||
key: SerializedKey,
|
||||
items: Cipher[],
|
||||
outputFormat: TOutputFormat = "text" as TOutputFormat
|
||||
) {
|
||||
const decryption = await loadDecryptionModule();
|
||||
const decryptedItems: Output<TOutputFormat>[] = [];
|
||||
for (const cipherData of items) {
|
||||
decryptedItems.push(decryption.decrypt(key, cipherData, outputFormat));
|
||||
}
|
||||
return decryptedItems;
|
||||
},
|
||||
createEncryptionStream: async function (key: SerializedKey) {
|
||||
const crypto = await loadNNCrypto();
|
||||
const stream = await crypto.createEncryptionStream(key);
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
return transfer(stream, [stream.stream]);
|
||||
},
|
||||
createDecryptionStream: async function (key: SerializedKey, iv: string) {
|
||||
const crypto = await loadNNCrypto();
|
||||
const obj = { stream: await crypto.createDecryptionStream(key, iv) };
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
return transfer(obj, [obj.stream]);
|
||||
}
|
||||
};
|
||||
|
||||
export type NNCryptoWorkerModule = typeof module;
|
||||
|
||||
expose(module);
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"extends": "../../tsconfig",
|
||||
"compilerOptions": {
|
||||
"lib": ["DOM", "WebWorker", "ES2015"],
|
||||
"outDir": "./dist"
|
||||
},
|
||||
"include": ["./index.ts", "./src/"]
|
||||
}
|
||||
@@ -26,9 +26,9 @@ import Password from "./src/password";
|
||||
import {
|
||||
Cipher,
|
||||
EncryptionKey,
|
||||
Input,
|
||||
Output,
|
||||
OutputFormat,
|
||||
Plaintext,
|
||||
DataFormat,
|
||||
SerializedKey
|
||||
} from "./src/types";
|
||||
|
||||
@@ -41,27 +41,45 @@ export class NNCrypto implements INNCrypto {
|
||||
this.isReady = true;
|
||||
}
|
||||
|
||||
async encrypt<TFormat extends OutputFormat>(
|
||||
async encrypt<TOutputFormat extends DataFormat>(
|
||||
key: SerializedKey,
|
||||
plaintext: Plaintext<TFormat>,
|
||||
outputFormat: OutputFormat = "uint8array"
|
||||
): Promise<Cipher> {
|
||||
input: Input<DataFormat>,
|
||||
format: DataFormat,
|
||||
outputFormat: TOutputFormat = "uint8array" as TOutputFormat
|
||||
): Promise<Cipher<TOutputFormat>> {
|
||||
await this.init();
|
||||
return Encryption.encrypt(key, plaintext, outputFormat);
|
||||
return Encryption.encrypt(
|
||||
key,
|
||||
input,
|
||||
format,
|
||||
outputFormat
|
||||
) as Cipher<TOutputFormat>;
|
||||
}
|
||||
|
||||
async decrypt<TOutputFormat extends OutputFormat>(
|
||||
async encryptMulti<TOutputFormat extends DataFormat>(
|
||||
key: SerializedKey,
|
||||
cipherData: Cipher,
|
||||
items: Input<DataFormat>[],
|
||||
format: DataFormat,
|
||||
outputFormat = "uint8array" as TOutputFormat
|
||||
): Promise<Cipher<TOutputFormat>[]> {
|
||||
await this.init();
|
||||
return items.map((data) =>
|
||||
Encryption.encrypt(key, data, format, outputFormat)
|
||||
);
|
||||
}
|
||||
|
||||
async decrypt<TOutputFormat extends DataFormat>(
|
||||
key: SerializedKey,
|
||||
cipherData: Cipher<DataFormat>,
|
||||
outputFormat: TOutputFormat = "text" as TOutputFormat
|
||||
): Promise<Output<TOutputFormat>> {
|
||||
await this.init();
|
||||
return Decryption.decrypt(key, cipherData, outputFormat);
|
||||
}
|
||||
|
||||
async decryptMulti<TOutputFormat extends OutputFormat>(
|
||||
async decryptMulti<TOutputFormat extends DataFormat>(
|
||||
key: SerializedKey,
|
||||
items: Cipher[],
|
||||
items: Cipher<DataFormat>[],
|
||||
outputFormat: TOutputFormat = "text" as TOutputFormat
|
||||
): Promise<Output<TOutputFormat>[]> {
|
||||
await this.init();
|
||||
|
||||
@@ -29,10 +29,10 @@ import {
|
||||
from_hex
|
||||
} from "@notesnook/sodium";
|
||||
import KeyUtils from "./keyutils";
|
||||
import { Cipher, Output, OutputFormat, SerializedKey } from "./types";
|
||||
import { Cipher, Output, DataFormat, SerializedKey } from "./types";
|
||||
|
||||
export default class Decryption {
|
||||
private static transformInput(cipherData: Cipher): Uint8Array {
|
||||
private static transformInput(cipherData: Cipher<DataFormat>): Uint8Array {
|
||||
let input: Uint8Array | null = null;
|
||||
if (
|
||||
typeof cipherData.cipher === "string" &&
|
||||
@@ -54,9 +54,9 @@ export default class Decryption {
|
||||
return input;
|
||||
}
|
||||
|
||||
static decrypt<TOutputFormat extends OutputFormat>(
|
||||
static decrypt<TOutputFormat extends DataFormat>(
|
||||
key: SerializedKey,
|
||||
cipherData: Cipher,
|
||||
cipherData: Cipher<DataFormat>,
|
||||
outputFormat: TOutputFormat = "text" as TOutputFormat
|
||||
): Output<TOutputFormat> {
|
||||
if (!key.salt && cipherData.salt) key.salt = cipherData.salt;
|
||||
|
||||
@@ -30,32 +30,34 @@ import {
|
||||
base64_variants
|
||||
} from "@notesnook/sodium";
|
||||
import KeyUtils from "./keyutils";
|
||||
import { Chunk, Cipher, OutputFormat, Plaintext, SerializedKey } from "./types";
|
||||
import { Chunk, Cipher, Input, DataFormat, SerializedKey } from "./types";
|
||||
|
||||
const encoder = new TextEncoder();
|
||||
export default class Encryption {
|
||||
private static transformInput(
|
||||
plaintext: Plaintext<OutputFormat>
|
||||
input: Input<DataFormat>,
|
||||
format: DataFormat
|
||||
): Uint8Array {
|
||||
let data: Uint8Array | null = null;
|
||||
if (typeof plaintext.data === "string" && plaintext.format === "base64") {
|
||||
data = from_base64(plaintext.data, base64_variants.ORIGINAL);
|
||||
} else if (typeof plaintext.data === "string") {
|
||||
data = encoder.encode(plaintext.data);
|
||||
} else if (plaintext.data instanceof Uint8Array) {
|
||||
data = plaintext.data;
|
||||
if (typeof input === "string" && format === "base64") {
|
||||
data = from_base64(input, base64_variants.ORIGINAL);
|
||||
} else if (typeof input === "string") {
|
||||
data = encoder.encode(input);
|
||||
} else if (input instanceof Uint8Array) {
|
||||
data = input;
|
||||
}
|
||||
if (!data) throw new Error("Data cannot be null.");
|
||||
return data;
|
||||
}
|
||||
|
||||
static encrypt(
|
||||
static encrypt<TOutputFormat extends DataFormat>(
|
||||
key: SerializedKey,
|
||||
plaintext: Plaintext<OutputFormat>,
|
||||
outputFormat: OutputFormat = "uint8array"
|
||||
): Cipher {
|
||||
input: Input<DataFormat>,
|
||||
format: DataFormat,
|
||||
outputFormat: TOutputFormat = "uint8array" as TOutputFormat
|
||||
): Cipher<TOutputFormat> {
|
||||
const encryptionKey = KeyUtils.transform(key);
|
||||
const data = this.transformInput(plaintext);
|
||||
const data = this.transformInput(input, format);
|
||||
|
||||
const nonce = randombytes_buf(crypto_aead_xchacha20poly1305_ietf_NPUBBYTES);
|
||||
|
||||
@@ -81,7 +83,7 @@ export default class Encryption {
|
||||
iv,
|
||||
salt: encryptionKey.salt,
|
||||
length: data.length
|
||||
};
|
||||
} as Cipher<TOutputFormat>;
|
||||
}
|
||||
|
||||
static createStream(key: SerializedKey): {
|
||||
|
||||
@@ -20,11 +20,11 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
import {
|
||||
Cipher,
|
||||
EncryptionKey,
|
||||
OutputFormat,
|
||||
Plaintext,
|
||||
DataFormat,
|
||||
SerializedKey,
|
||||
Chunk,
|
||||
Output
|
||||
Output,
|
||||
Input
|
||||
} from "./types";
|
||||
|
||||
export interface IStreamable {
|
||||
@@ -33,21 +33,29 @@ export interface IStreamable {
|
||||
}
|
||||
|
||||
export interface INNCrypto {
|
||||
encrypt(
|
||||
encrypt<TOutputFormat extends DataFormat>(
|
||||
key: SerializedKey,
|
||||
plaintext: Plaintext<OutputFormat>,
|
||||
outputFormat?: OutputFormat
|
||||
): Promise<Cipher>;
|
||||
data: Input<DataFormat>,
|
||||
format: DataFormat,
|
||||
outputFormat?: TOutputFormat
|
||||
): Promise<Cipher<TOutputFormat>>;
|
||||
|
||||
decrypt<TOutputFormat extends OutputFormat>(
|
||||
encryptMulti<TOutputFormat extends DataFormat>(
|
||||
key: SerializedKey,
|
||||
cipherData: Cipher,
|
||||
data: Input<DataFormat>[],
|
||||
format: DataFormat,
|
||||
outputFormat?: TOutputFormat
|
||||
): Promise<Cipher<TOutputFormat>[]>;
|
||||
|
||||
decrypt<TOutputFormat extends DataFormat>(
|
||||
key: SerializedKey,
|
||||
cipherData: Cipher<DataFormat>,
|
||||
outputFormat?: TOutputFormat
|
||||
): Promise<Output<TOutputFormat>>;
|
||||
|
||||
decryptMulti<TOutputFormat extends OutputFormat>(
|
||||
decryptMulti<TOutputFormat extends DataFormat>(
|
||||
key: SerializedKey,
|
||||
cipherData: Cipher[],
|
||||
cipherData: Cipher<DataFormat>[],
|
||||
outputFormat?: TOutputFormat
|
||||
): Promise<Output<TOutputFormat>[]>;
|
||||
|
||||
|
||||
@@ -19,23 +19,20 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { StringOutputFormat, Uint8ArrayOutputFormat } from "@notesnook/sodium";
|
||||
|
||||
export type OutputFormat = Uint8ArrayOutputFormat | StringOutputFormat;
|
||||
export type DataFormat = Uint8ArrayOutputFormat | StringOutputFormat;
|
||||
|
||||
export type Cipher = {
|
||||
format: OutputFormat;
|
||||
export type Cipher<TFormat extends DataFormat> = {
|
||||
format: TFormat;
|
||||
alg: string;
|
||||
cipher: string | Uint8Array;
|
||||
cipher: Output<TFormat>;
|
||||
iv: string;
|
||||
salt: string;
|
||||
length: number;
|
||||
};
|
||||
|
||||
export type Plaintext<TFormat extends OutputFormat> = {
|
||||
format: TFormat;
|
||||
data: TFormat extends StringOutputFormat ? string : Uint8Array;
|
||||
};
|
||||
export type Output<TFormat extends OutputFormat> =
|
||||
export type Output<TFormat extends DataFormat> =
|
||||
TFormat extends StringOutputFormat ? string : Uint8Array;
|
||||
export type Input<TFormat extends DataFormat> = Output<TFormat>;
|
||||
|
||||
export type SerializedKey = {
|
||||
password?: string;
|
||||
|
||||
Reference in New Issue
Block a user