crypto: remove crypto-worker & simplify crypto interface

This commit is contained in:
Abdullah Atta
2023-09-01 09:32:06 +05:00
committed by Abdullah Atta
parent 6554f900d7
commit fac788d2a9
18 changed files with 1342 additions and 788 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -20,7 +20,6 @@
"@notesnook/common": "file:../../packages/common", "@notesnook/common": "file:../../packages/common",
"@notesnook/core": "file:../../packages/core", "@notesnook/core": "file:../../packages/core",
"@notesnook/crypto": "file:../../packages/crypto", "@notesnook/crypto": "file:../../packages/crypto",
"@notesnook/crypto-worker": "file:../../packages/crypto-worker",
"@notesnook/desktop": "file:../desktop", "@notesnook/desktop": "file:../desktop",
"@notesnook/editor": "file:../../packages/editor", "@notesnook/editor": "file:../../packages/editor",
"@notesnook/logger": "file:../../packages/logger", "@notesnook/logger": "file:../../packages/logger",

View File

@@ -22,7 +22,7 @@ import { xxhash64, createXXHash64 } from "hash-wasm";
import axios, { AxiosProgressEvent } from "axios"; import axios, { AxiosProgressEvent } from "axios";
import { AppEventManager, AppEvents } from "../common/app-events"; import { AppEventManager, AppEvents } from "../common/app-events";
import { StreamableFS } from "@notesnook/streamable-fs"; import { StreamableFS } from "@notesnook/streamable-fs";
import { getNNCrypto } from "./nncrypto.stub"; import { NNCrypto } from "./nncrypto";
import hosts from "@notesnook/core/dist/utils/constants"; import hosts from "@notesnook/core/dist/utils/constants";
import { sendAttachmentsProgressEvent } from "@notesnook/core/dist/common"; import { sendAttachmentsProgressEvent } from "@notesnook/core/dist/common";
import { saveAs } from "file-saver"; import { saveAs } from "file-saver";
@@ -34,7 +34,7 @@ import { ProgressStream } from "../utils/streams/progress-stream";
import { consumeReadableStream } from "../utils/stream"; import { consumeReadableStream } from "../utils/stream";
import { Base64DecoderStream } from "../utils/streams/base64-decoder-stream"; import { Base64DecoderStream } from "../utils/streams/base64-decoder-stream";
import { toBlob } from "@notesnook-importer/core/dist/src/utils/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 { IDataType } from "hash-wasm/dist/lib/util";
import { IndexedDBKVStore } from "./key-value"; import { IndexedDBKVStore } from "./key-value";
import FileHandle from "@notesnook/streamable-fs/dist/src/filehandle"; import FileHandle from "@notesnook/streamable-fs/dist/src/filehandle";
@@ -53,8 +53,6 @@ async function writeEncryptedFile(
key: SerializedKey, key: SerializedKey,
hash: string hash: string
) { ) {
const crypto = await getNNCrypto();
if (!IndexedDBKVStore.isIndexedDBSupported()) if (!IndexedDBKVStore.isIndexedDBSupported())
throw new Error("This browser does not support IndexedDB."); 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); const fileHandle = await streamablefs.createFile(hash, file.size, file.type);
sendAttachmentsProgressEvent("encrypt", hash, 1, 0); sendAttachmentsProgressEvent("encrypt", hash, 1, 0);
const { iv, stream } = await crypto.createEncryptionStream(key); const { iv, stream } = await NNCrypto.createEncryptionStream(key);
await file await file
.stream() .stream()
.pipeThrough(new ChunkedStream(CHUNK_SIZE)) .pipeThrough(new ChunkedStream(CHUNK_SIZE))
@@ -84,7 +82,7 @@ async function writeEncryptedFile(
) )
.pipeTo(fileHandle.writeable); .pipeTo(fileHandle.writeable);
sendAttachmentsProgressEvent("encrypt", hash, 1); sendAttachmentsProgressEvent("encrypt", hash, 1, 1);
return { return {
chunkSize: CHUNK_SIZE, chunkSize: CHUNK_SIZE,
@@ -155,15 +153,14 @@ async function hashStream(reader: ReadableStreamDefaultReader<Uint8Array>) {
async function readEncrypted( async function readEncrypted(
filename: string, filename: string,
key: SerializedKey, key: SerializedKey,
cipherData: Cipher & { outputType: OutputFormat } cipherData: Cipher<DataFormat> & { outputType: DataFormat }
) { ) {
const fileHandle = await streamablefs.readFile(filename); const fileHandle = await streamablefs.readFile(filename);
if (!fileHandle) { if (!fileHandle) {
console.error(`File not found. (File hash: ${filename})`); console.error(`File not found. (File hash: ${filename})`);
return null; return null;
} }
const crypto = await getNNCrypto(); const decryptionStream = await NNCrypto.createDecryptionStream(
const decryptionStream = await crypto.createDecryptionStream(
key, key,
cipherData.iv cipherData.iv
); );
@@ -527,8 +524,7 @@ export async function decryptFile(
const { key, iv } = fileMetadata; const { key, iv } = fileMetadata;
const crypto = await getNNCrypto(); const decryptionStream = await NNCrypto.createDecryptionStream(key, iv);
const decryptionStream = await crypto.createDecryptionStream(key, iv);
return await toBlob(fileHandle.readable.pipeThrough(decryptionStream)); return await toBlob(fileHandle.readable.pipeThrough(decryptionStream));
} }

View File

@@ -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;
}

View 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;

View 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());

View File

@@ -23,7 +23,7 @@ import {
MemoryKVStore, MemoryKVStore,
IKVStore IKVStore
} from "./key-value"; } from "./key-value";
import { getNNCrypto } from "./nncrypto.stub"; import { NNCrypto } from "./nncrypto";
import type { Cipher, SerializedKey } from "@notesnook/crypto/dist/src/types"; import type { Cipher, SerializedKey } from "@notesnook/crypto/dist/src/types";
type EncryptedKey = { iv: Uint8Array; cipher: BufferSource }; type EncryptedKey = { iv: Uint8Array; cipher: BufferSource };
@@ -82,8 +82,7 @@ export class NNStorage {
const { password, salt } = credentials; const { password, salt } = credentials;
if (!password) throw new Error("Invalid data provided to deriveCryptoKey."); if (!password) throw new Error("Invalid data provided to deriveCryptoKey.");
const crypto = await getNNCrypto(); const keyData = await NNCrypto.exportKey(password, salt);
const keyData = await crypto.exportKey(password, salt);
if ( if (
(await IndexedDBKVStore.isIndexedDBSupported()) && (await IndexedDBKVStore.isIndexedDBSupported()) &&
@@ -124,41 +123,39 @@ export class NNStorage {
): Promise<SerializedKey> { ): Promise<SerializedKey> {
if (!password) if (!password)
throw new Error("Invalid data provided to generateCryptoKey."); 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> { async hash(password: string, email: string): Promise<string> {
const crypto = await getNNCrypto(); return await NNCrypto.hash(password, `${APP_SALT}${email}`);
return await crypto.hash(password, `${APP_SALT}${email}`);
} }
async encrypt(key: SerializedKey, plainText: string): Promise<Cipher> { encrypt(key: SerializedKey, plainText: string): Promise<Cipher<"base64">> {
const crypto = await getNNCrypto(); return NNCrypto.encrypt(key, plainText, "text", "base64");
return await crypto.encrypt(
key,
{ format: "text", data: plainText },
"base64"
);
} }
async decrypt( encryptMulti(
key: SerializedKey, 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> { ): Promise<string | undefined> {
const crypto = await getNNCrypto();
cipherData.format = "base64"; cipherData.format = "base64";
return await crypto.decrypt(key, cipherData, "text"); return NNCrypto.decrypt(key, cipherData, "text");
} }
async decryptMulti( decryptMulti(
key: SerializedKey, key: SerializedKey,
items: Cipher[] items: Cipher<"base64">[]
): Promise<string[] | undefined> { ): Promise<string[] | undefined> {
const crypto = await getNNCrypto();
items.forEach((c) => (c.format = "base64")); 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); 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);

View File

@@ -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();
// }
// }
// }
// };
// }
}

View File

@@ -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=="
}
}
}

View File

@@ -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": {}
}

View File

@@ -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);

View File

@@ -1,8 +0,0 @@
{
"extends": "../../tsconfig",
"compilerOptions": {
"lib": ["DOM", "WebWorker", "ES2015"],
"outDir": "./dist"
},
"include": ["./index.ts", "./src/"]
}

View File

@@ -26,9 +26,9 @@ import Password from "./src/password";
import { import {
Cipher, Cipher,
EncryptionKey, EncryptionKey,
Input,
Output, Output,
OutputFormat, DataFormat,
Plaintext,
SerializedKey SerializedKey
} from "./src/types"; } from "./src/types";
@@ -41,27 +41,45 @@ export class NNCrypto implements INNCrypto {
this.isReady = true; this.isReady = true;
} }
async encrypt<TFormat extends OutputFormat>( async encrypt<TOutputFormat extends DataFormat>(
key: SerializedKey, key: SerializedKey,
plaintext: Plaintext<TFormat>, input: Input<DataFormat>,
outputFormat: OutputFormat = "uint8array" format: DataFormat,
): Promise<Cipher> { outputFormat: TOutputFormat = "uint8array" as TOutputFormat
): Promise<Cipher<TOutputFormat>> {
await this.init(); 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, 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 outputFormat: TOutputFormat = "text" as TOutputFormat
): Promise<Output<TOutputFormat>> { ): Promise<Output<TOutputFormat>> {
await this.init(); await this.init();
return Decryption.decrypt(key, cipherData, outputFormat); return Decryption.decrypt(key, cipherData, outputFormat);
} }
async decryptMulti<TOutputFormat extends OutputFormat>( async decryptMulti<TOutputFormat extends DataFormat>(
key: SerializedKey, key: SerializedKey,
items: Cipher[], items: Cipher<DataFormat>[],
outputFormat: TOutputFormat = "text" as TOutputFormat outputFormat: TOutputFormat = "text" as TOutputFormat
): Promise<Output<TOutputFormat>[]> { ): Promise<Output<TOutputFormat>[]> {
await this.init(); await this.init();

View File

@@ -29,10 +29,10 @@ import {
from_hex from_hex
} from "@notesnook/sodium"; } from "@notesnook/sodium";
import KeyUtils from "./keyutils"; import KeyUtils from "./keyutils";
import { Cipher, Output, OutputFormat, SerializedKey } from "./types"; import { Cipher, Output, DataFormat, SerializedKey } from "./types";
export default class Decryption { export default class Decryption {
private static transformInput(cipherData: Cipher): Uint8Array { private static transformInput(cipherData: Cipher<DataFormat>): Uint8Array {
let input: Uint8Array | null = null; let input: Uint8Array | null = null;
if ( if (
typeof cipherData.cipher === "string" && typeof cipherData.cipher === "string" &&
@@ -54,9 +54,9 @@ export default class Decryption {
return input; return input;
} }
static decrypt<TOutputFormat extends OutputFormat>( static decrypt<TOutputFormat extends DataFormat>(
key: SerializedKey, key: SerializedKey,
cipherData: Cipher, cipherData: Cipher<DataFormat>,
outputFormat: TOutputFormat = "text" as TOutputFormat outputFormat: TOutputFormat = "text" as TOutputFormat
): Output<TOutputFormat> { ): Output<TOutputFormat> {
if (!key.salt && cipherData.salt) key.salt = cipherData.salt; if (!key.salt && cipherData.salt) key.salt = cipherData.salt;

View File

@@ -30,32 +30,34 @@ import {
base64_variants base64_variants
} from "@notesnook/sodium"; } from "@notesnook/sodium";
import KeyUtils from "./keyutils"; import KeyUtils from "./keyutils";
import { Chunk, Cipher, OutputFormat, Plaintext, SerializedKey } from "./types"; import { Chunk, Cipher, Input, DataFormat, SerializedKey } from "./types";
const encoder = new TextEncoder(); const encoder = new TextEncoder();
export default class Encryption { export default class Encryption {
private static transformInput( private static transformInput(
plaintext: Plaintext<OutputFormat> input: Input<DataFormat>,
format: DataFormat
): Uint8Array { ): Uint8Array {
let data: Uint8Array | null = null; let data: Uint8Array | null = null;
if (typeof plaintext.data === "string" && plaintext.format === "base64") { if (typeof input === "string" && format === "base64") {
data = from_base64(plaintext.data, base64_variants.ORIGINAL); data = from_base64(input, base64_variants.ORIGINAL);
} else if (typeof plaintext.data === "string") { } else if (typeof input === "string") {
data = encoder.encode(plaintext.data); data = encoder.encode(input);
} else if (plaintext.data instanceof Uint8Array) { } else if (input instanceof Uint8Array) {
data = plaintext.data; data = input;
} }
if (!data) throw new Error("Data cannot be null."); if (!data) throw new Error("Data cannot be null.");
return data; return data;
} }
static encrypt( static encrypt<TOutputFormat extends DataFormat>(
key: SerializedKey, key: SerializedKey,
plaintext: Plaintext<OutputFormat>, input: Input<DataFormat>,
outputFormat: OutputFormat = "uint8array" format: DataFormat,
): Cipher { outputFormat: TOutputFormat = "uint8array" as TOutputFormat
): Cipher<TOutputFormat> {
const encryptionKey = KeyUtils.transform(key); 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); const nonce = randombytes_buf(crypto_aead_xchacha20poly1305_ietf_NPUBBYTES);
@@ -81,7 +83,7 @@ export default class Encryption {
iv, iv,
salt: encryptionKey.salt, salt: encryptionKey.salt,
length: data.length length: data.length
}; } as Cipher<TOutputFormat>;
} }
static createStream(key: SerializedKey): { static createStream(key: SerializedKey): {

View File

@@ -20,11 +20,11 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { import {
Cipher, Cipher,
EncryptionKey, EncryptionKey,
OutputFormat, DataFormat,
Plaintext,
SerializedKey, SerializedKey,
Chunk, Chunk,
Output Output,
Input
} from "./types"; } from "./types";
export interface IStreamable { export interface IStreamable {
@@ -33,21 +33,29 @@ export interface IStreamable {
} }
export interface INNCrypto { export interface INNCrypto {
encrypt( encrypt<TOutputFormat extends DataFormat>(
key: SerializedKey, key: SerializedKey,
plaintext: Plaintext<OutputFormat>, data: Input<DataFormat>,
outputFormat?: OutputFormat format: DataFormat,
): Promise<Cipher>; outputFormat?: TOutputFormat
): Promise<Cipher<TOutputFormat>>;
decrypt<TOutputFormat extends OutputFormat>( encryptMulti<TOutputFormat extends DataFormat>(
key: SerializedKey, 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 outputFormat?: TOutputFormat
): Promise<Output<TOutputFormat>>; ): Promise<Output<TOutputFormat>>;
decryptMulti<TOutputFormat extends OutputFormat>( decryptMulti<TOutputFormat extends DataFormat>(
key: SerializedKey, key: SerializedKey,
cipherData: Cipher[], cipherData: Cipher<DataFormat>[],
outputFormat?: TOutputFormat outputFormat?: TOutputFormat
): Promise<Output<TOutputFormat>[]>; ): Promise<Output<TOutputFormat>[]>;

View File

@@ -19,23 +19,20 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { StringOutputFormat, Uint8ArrayOutputFormat } from "@notesnook/sodium"; import { StringOutputFormat, Uint8ArrayOutputFormat } from "@notesnook/sodium";
export type OutputFormat = Uint8ArrayOutputFormat | StringOutputFormat; export type DataFormat = Uint8ArrayOutputFormat | StringOutputFormat;
export type Cipher = { export type Cipher<TFormat extends DataFormat> = {
format: OutputFormat; format: TFormat;
alg: string; alg: string;
cipher: string | Uint8Array; cipher: Output<TFormat>;
iv: string; iv: string;
salt: string; salt: string;
length: number; length: number;
}; };
export type Plaintext<TFormat extends OutputFormat> = { export type Output<TFormat extends DataFormat> =
format: TFormat;
data: TFormat extends StringOutputFormat ? string : Uint8Array;
};
export type Output<TFormat extends OutputFormat> =
TFormat extends StringOutputFormat ? string : Uint8Array; TFormat extends StringOutputFormat ? string : Uint8Array;
export type Input<TFormat extends DataFormat> = Output<TFormat>;
export type SerializedKey = { export type SerializedKey = {
password?: string; password?: string;