diff --git a/packages/crypto/src/decryption.ts b/packages/crypto/src/decryption.ts index da1246405..487b6414f 100644 --- a/packages/crypto/src/decryption.ts +++ b/packages/crypto/src/decryption.ts @@ -17,28 +17,21 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ -import { - crypto_aead_xchacha20poly1305_ietf_decrypt, - crypto_secretstream_xchacha20poly1305_init_pull, - crypto_secretstream_xchacha20poly1305_pull, - to_base64, - from_base64, - base64_variants, - to_string, - crypto_secretstream_xchacha20poly1305_TAG_FINAL, - from_hex -} from "@notesnook/sodium"; +import { base64_variants, ISodium } from "@notesnook/sodium"; import KeyUtils from "./keyutils.js"; import { Cipher, Output, DataFormat, SerializedKey } from "./types.js"; export default class Decryption { - private static transformInput(cipherData: Cipher): Uint8Array { + private static transformInput( + sodium: ISodium, + cipherData: Cipher + ): Uint8Array { let input: Uint8Array | null = null; if ( typeof cipherData.cipher === "string" && cipherData.format === "base64" ) { - input = from_base64( + input = sodium.from_base64( cipherData.cipher, base64_variants.URLSAFE_NO_PADDING ); @@ -46,7 +39,7 @@ export default class Decryption { typeof cipherData.cipher === "string" && cipherData.format === "hex" ) { - input = from_hex(cipherData.cipher); + input = sodium.from_hex(cipherData.cipher); } else if (cipherData.cipher instanceof Uint8Array) { input = cipherData.cipher; } @@ -55,52 +48,51 @@ export default class Decryption { } static decrypt( + sodium: ISodium, key: SerializedKey, cipherData: Cipher, outputFormat: TOutputFormat = "text" as TOutputFormat ): Output { if (!key.salt && cipherData.salt) key.salt = cipherData.salt; - const encryptionKey = KeyUtils.transform(key); + const encryptionKey = KeyUtils.transform(sodium, key); - const input = this.transformInput(cipherData); - const plaintext = crypto_aead_xchacha20poly1305_ietf_decrypt( + const input = this.transformInput(sodium, cipherData); + const plaintext = sodium.crypto_aead_xchacha20poly1305_ietf_decrypt( null, input, null, - from_base64(cipherData.iv), + sodium.from_base64(cipherData.iv), encryptionKey.key ); return ( outputFormat === "base64" - ? to_base64(plaintext, base64_variants.ORIGINAL) + ? sodium.to_base64(plaintext, base64_variants.ORIGINAL) : outputFormat === "text" - ? to_string(plaintext) + ? sodium.to_string(plaintext) : plaintext ) as Output; } static createStream( + sodium: ISodium, header: string, key: SerializedKey ): TransformStream { - const { key: _key } = KeyUtils.transform(key); - const state = crypto_secretstream_xchacha20poly1305_init_pull( - from_base64(header), + const { key: _key } = KeyUtils.transform(sodium, key); + const state = sodium.crypto_secretstream_xchacha20poly1305_init_pull( + sodium.from_base64(header), _key ); return new TransformStream({ start() {}, transform(chunk, controller) { - const { message, tag } = crypto_secretstream_xchacha20poly1305_pull( - state, - chunk, - null - ); + const { message, tag } = + sodium.crypto_secretstream_xchacha20poly1305_pull(state, chunk, null); if (!message) throw new Error("Could not decrypt chunk."); controller.enqueue(message); - if (tag === crypto_secretstream_xchacha20poly1305_TAG_FINAL) + if (tag === sodium.crypto_secretstream_xchacha20poly1305_TAG_FINAL) controller.terminate(); } }); diff --git a/packages/crypto/src/encryption.ts b/packages/crypto/src/encryption.ts index d93c294f7..99cdc9d17 100644 --- a/packages/crypto/src/encryption.ts +++ b/packages/crypto/src/encryption.ts @@ -17,30 +17,20 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ -import { - crypto_aead_xchacha20poly1305_ietf_encrypt, - crypto_secretstream_xchacha20poly1305_init_push, - crypto_secretstream_xchacha20poly1305_push, - randombytes_buf, - crypto_aead_xchacha20poly1305_ietf_NPUBBYTES, - crypto_secretstream_xchacha20poly1305_TAG_FINAL, - crypto_secretstream_xchacha20poly1305_TAG_MESSAGE, - to_base64, - from_base64, - base64_variants -} from "@notesnook/sodium"; +import { ISodium, base64_variants } from "@notesnook/sodium"; import KeyUtils from "./keyutils.js"; import { Chunk, Cipher, Input, DataFormat, SerializedKey } from "./types.js"; const encoder = new TextEncoder(); export default class Encryption { private static transformInput( + sodium: ISodium, input: Input, format: DataFormat ): Uint8Array { let data: Uint8Array | null = null; if (typeof input === "string" && format === "base64") { - data = from_base64(input, base64_variants.ORIGINAL); + data = sodium.from_base64(input, base64_variants.ORIGINAL); } else if (typeof input === "string") { data = encoder.encode(input); } else if (input instanceof Uint8Array) { @@ -51,18 +41,21 @@ export default class Encryption { } static encrypt( + sodium: ISodium, key: SerializedKey, input: Input, format: DataFormat, outputFormat: TOutputFormat = "uint8array" as TOutputFormat ): Cipher { - const encryptionKey = KeyUtils.transform(key); - const data = this.transformInput(input, format); + const encryptionKey = KeyUtils.transform(sodium, key); + const data = this.transformInput(sodium, input, format); - const nonce = randombytes_buf(crypto_aead_xchacha20poly1305_ietf_NPUBBYTES); + const nonce = sodium.randombytes_buf( + sodium.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES + ); const cipher: string | Uint8Array = - crypto_aead_xchacha20poly1305_ietf_encrypt( + sodium.crypto_aead_xchacha20poly1305_ietf_encrypt( data, null, null, @@ -72,10 +65,10 @@ export default class Encryption { let output: string | Uint8Array = cipher; if (outputFormat === "base64") { - output = to_base64(cipher, base64_variants.URLSAFE_NO_PADDING); + output = sodium.to_base64(cipher, base64_variants.URLSAFE_NO_PADDING); } - const iv = to_base64(nonce); + const iv = sodium.to_base64(nonce); return { format: outputFormat, alg: getAlgorithm(base64_variants.URLSAFE_NO_PADDING), @@ -86,15 +79,16 @@ export default class Encryption { } as Cipher; } - static createStream(key: SerializedKey): { + static createStream( + sodium: ISodium, + key: SerializedKey + ): { iv: string; stream: TransformStream; } { - const { key: _key } = KeyUtils.transform(key); - const { state, header } = crypto_secretstream_xchacha20poly1305_init_push( - _key, - "base64" - ); + const { key: _key } = KeyUtils.transform(sodium, key); + const { state, header } = + sodium.crypto_secretstream_xchacha20poly1305_init_push(_key, "base64"); return { iv: header, @@ -102,13 +96,13 @@ export default class Encryption { start() {}, transform(chunk, controller) { controller.enqueue( - crypto_secretstream_xchacha20poly1305_push( + sodium.crypto_secretstream_xchacha20poly1305_push( state, chunk.data, null, chunk.final - ? crypto_secretstream_xchacha20poly1305_TAG_FINAL - : crypto_secretstream_xchacha20poly1305_TAG_MESSAGE + ? sodium.crypto_secretstream_xchacha20poly1305_TAG_FINAL + : sodium.crypto_secretstream_xchacha20poly1305_TAG_MESSAGE ) ); if (chunk.final) controller.terminate(); @@ -118,30 +112,6 @@ export default class Encryption { } } -// class EncryptionStream { -// state: StateAddress; -// header: string; -// constructor(key: EncryptionKey) { -// const { state, header } = crypto_secretstream_xchacha20poly1305_init_push( -// key.key, -// "base64" -// ); -// this.state = state; -// this.header = header; -// } - -// write(chunk: Uint8Array, final?: boolean): Uint8Array { -// return crypto_secretstream_xchacha20poly1305_push( -// this.state, -// chunk, -// null, -// final -// ? crypto_secretstream_xchacha20poly1305_TAG_FINAL -// : crypto_secretstream_xchacha20poly1305_TAG_MESSAGE -// ); -// } -// } - function getAlgorithm(base64Variant: base64_variants) { //Template: encryptionAlgorithm-kdfAlgorithm-base64variant return `xcha-argon2i13-${base64Variant}`; diff --git a/packages/crypto/src/index.ts b/packages/crypto/src/index.ts index 22eeedbed..00f93f96d 100644 --- a/packages/crypto/src/index.ts +++ b/packages/crypto/src/index.ts @@ -17,7 +17,7 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ -import { initialize } from "@notesnook/sodium"; +import { ISodium, Sodium } from "@notesnook/sodium"; import Decryption from "./decryption.js"; import Encryption from "./encryption.js"; import { INNCrypto } from "./interfaces.js"; @@ -34,10 +34,11 @@ import { export class NNCrypto implements INNCrypto { private isReady = false; + private sodium: ISodium = new Sodium(); private async init() { if (this.isReady) return; - await initialize(); + await this.sodium.initialize(); this.isReady = true; } @@ -49,6 +50,7 @@ export class NNCrypto implements INNCrypto { ): Promise> { await this.init(); return Encryption.encrypt( + this.sodium, key, input, format, @@ -64,7 +66,7 @@ export class NNCrypto implements INNCrypto { ): Promise[]> { await this.init(); return items.map((data) => - Encryption.encrypt(key, data, format, outputFormat) + Encryption.encrypt(this.sodium, key, data, format, outputFormat) ); } @@ -74,7 +76,7 @@ export class NNCrypto implements INNCrypto { outputFormat: TOutputFormat = "text" as TOutputFormat ): Promise> { await this.init(); - return Decryption.decrypt(key, cipherData, outputFormat); + return Decryption.decrypt(this.sodium, key, cipherData, outputFormat); } async decryptMulti( @@ -85,88 +87,37 @@ export class NNCrypto implements INNCrypto { await this.init(); const decryptedItems: Output[] = []; for (const cipherData of items) { - decryptedItems.push(Decryption.decrypt(key, cipherData, outputFormat)); + decryptedItems.push( + Decryption.decrypt(this.sodium, key, cipherData, outputFormat) + ); } return decryptedItems; } async hash(password: string, salt: string): Promise { await this.init(); - return Password.hash(password, salt); + return Password.hash(this.sodium, password, salt); } async deriveKey(password: string, salt?: string): Promise { await this.init(); - return KeyUtils.deriveKey(password, salt); + return KeyUtils.deriveKey(this.sodium, password, salt); } async exportKey(password: string, salt?: string): Promise { await this.init(); - return KeyUtils.exportKey(password, salt); + return KeyUtils.exportKey(this.sodium, password, salt); } async createEncryptionStream(key: SerializedKey) { await this.init(); - return Encryption.createStream(key); - - // // eslint-disable-next-line no-constant-condition - // while (true) { - // const chunk = await stream.read(); - // if (!chunk) break; - - // const { data, final } = chunk; - // if (!data) break; - - // const encryptedChunk: Chunk = { - // data: encryptionStream.write(data, final), - // final - // }; - // await stream.write(encryptedChunk); - - // if (final) break; - // } - // return encryptionStream.header; + return Encryption.createStream(this.sodium, key); } async createDecryptionStream(key: SerializedKey, iv: string) { await this.init(); - return Decryption.createStream(iv, key); - // eslint-disable-next-line no-constant-condition - // while (true) { - // const chunk = await stream.read(); - // if (!chunk) break; - - // const { data, final } = chunk; - // if (!data) break; - - // const decryptedChunk: Chunk = { - // data: decryptionStream.read(data), - // final - // }; - // await stream.write(decryptedChunk); - - // if (final) break; - // } + return Decryption.createStream(this.sodium, iv, key); } - - // async encryptStream( - // key: SerializedKey, - // stream: IStreamable, - // _streamId?: string - // ): Promise { - // await this.init(); - // return await this.createEncryptionStream(key, stream); - // } - - // async decryptStream( - // key: SerializedKey, - // iv: string, - // stream: IStreamable, - // _streamId?: string - // ): Promise { - // await this.init(); - // await this.createDecryptionStream(iv, key, stream); - // } } export * from "./types.js"; diff --git a/packages/crypto/src/keyutils.ts b/packages/crypto/src/keyutils.ts index 6c72032b9..0cc20bd7d 100644 --- a/packages/crypto/src/keyutils.ts +++ b/packages/crypto/src/keyutils.ts @@ -17,46 +17,47 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ -import { - from_base64, - to_base64, - randombytes_buf, - crypto_pwhash, - crypto_pwhash_SALTBYTES, - crypto_pwhash_ALG_ARGON2I13, - crypto_aead_xchacha20poly1305_ietf_KEYBYTES -} from "@notesnook/sodium"; +import { ISodium } from "@notesnook/sodium"; import { EncryptionKey, SerializedKey } from "./types.js"; export default class KeyUtils { - static deriveKey(password: string, salt?: string): EncryptionKey { + static deriveKey( + sodium: ISodium, + password: string, + salt?: string + ): EncryptionKey { let saltBytes: Uint8Array; - if (!salt) saltBytes = randombytes_buf(crypto_pwhash_SALTBYTES); + if (!salt) + saltBytes = sodium.randombytes_buf(sodium.crypto_pwhash_SALTBYTES); else { - saltBytes = from_base64(salt); + saltBytes = sodium.from_base64(salt); } if (!saltBytes) throw new Error("Could not generate bytes from the given salt."); - const key = crypto_pwhash( - crypto_aead_xchacha20poly1305_ietf_KEYBYTES, + const key = sodium.crypto_pwhash( + sodium.crypto_aead_xchacha20poly1305_ietf_KEYBYTES, password, saltBytes, 3, // operations limit 1024 * 1024 * 8, // memory limit (8MB) - crypto_pwhash_ALG_ARGON2I13 + sodium.crypto_pwhash_ALG_ARGON2I13 ); return { key, - salt: typeof salt === "string" ? salt : to_base64(saltBytes) + salt: typeof salt === "string" ? salt : sodium.to_base64(saltBytes) }; } - static exportKey(password: string, salt?: string): SerializedKey { - const { key, salt: keySalt } = this.deriveKey(password, salt); - return { key: to_base64(key), salt: keySalt }; + static exportKey( + sodium: ISodium, + password: string, + salt?: string + ): SerializedKey { + const { key, salt: keySalt } = this.deriveKey(sodium, password, salt); + return { key: sodium.to_base64(key), salt: keySalt }; } /** @@ -64,12 +65,12 @@ export default class KeyUtils { * and spits out a key that can be directly used for encryption/decryption. * @param input */ - static transform(input: SerializedKey): EncryptionKey { + static transform(sodium: ISodium, input: SerializedKey): EncryptionKey { if ("password" in input && !!input.password) { const { password, salt } = input; - return this.deriveKey(password, salt); + return this.deriveKey(sodium, password, salt); } else if ("key" in input && !!input.salt && !!input.key) { - return { key: from_base64(input.key), salt: input.salt }; + return { key: sodium.from_base64(input.key), salt: input.salt }; } throw new Error("Invalid input."); } diff --git a/packages/crypto/src/password.ts b/packages/crypto/src/password.ts index 0a7e24a1f..b987728e9 100644 --- a/packages/crypto/src/password.ts +++ b/packages/crypto/src/password.ts @@ -17,23 +17,21 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ -import { - crypto_generichash, - crypto_pwhash, - crypto_pwhash_ALG_ARGON2ID13, - crypto_pwhash_SALTBYTES -} from "@notesnook/sodium"; +import { ISodium } from "@notesnook/sodium"; export default class Password { - static hash(password: string, salt: string): string { - const saltBytes = crypto_generichash(crypto_pwhash_SALTBYTES, salt); - const hash = crypto_pwhash( + static hash(sodium: ISodium, password: string, salt: string): string { + const saltBytes = sodium.crypto_generichash( + sodium.crypto_pwhash_SALTBYTES, + salt + ); + const hash = sodium.crypto_pwhash( 32, password, saltBytes, 3, // operations limit 1024 * 1024 * 64, // memory limit (8MB) - crypto_pwhash_ALG_ARGON2ID13, + sodium.crypto_pwhash_ALG_ARGON2ID13, "base64" ); return hash; diff --git a/packages/sodium/benches/bench.ts b/packages/sodium/benches/bench.ts index 771bb8807..88993ae34 100644 --- a/packages/sodium/benches/bench.ts +++ b/packages/sodium/benches/bench.ts @@ -16,8 +16,9 @@ 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 . */ -import * as browser from "../src/browser.js"; -import * as node from "../src/node.js"; + +import { Sodium as BrowserSodium } from "../src/browser"; +import { Sodium as NodeSodium } from "../src/node"; import benny from "benny"; import { decrypt, @@ -25,7 +26,10 @@ import { getKey, hash, streamingEncrypt -} from "../tests/utils.js"; +} from "../tests/utils"; + +const browser = new BrowserSodium(); +const node = new NodeSodium(); async function main() { await browser.initialize(); diff --git a/packages/sodium/src/browser.ts b/packages/sodium/src/browser.ts index f412ae121..5a2528320 100644 --- a/packages/sodium/src/browser.ts +++ b/packages/sodium/src/browser.ts @@ -18,45 +18,125 @@ along with this program. If not, see . */ import sodium from "libsodium-wrappers-sumo"; +import { base64_variants, type ISodium } from "./types"; -export async function initialize() { - await sodium.ready; -} +export class Sodium implements ISodium { + async initialize() { + await sodium.ready; + } -export { - crypto_generichash, - crypto_pwhash, - crypto_pwhash_ALG_ARGON2ID13, - crypto_pwhash_SALTBYTES, - crypto_pwhash_ALG_ARGON2I13, - crypto_pwhash_ALG_DEFAULT, - crypto_pwhash_OPSLIMIT_INTERACTIVE, - crypto_pwhash_OPSLIMIT_MODERATE, - crypto_pwhash_OPSLIMIT_SENSITIVE, - crypto_pwhash_MEMLIMIT_INTERACTIVE, - crypto_pwhash_MEMLIMIT_MODERATE, - crypto_pwhash_MEMLIMIT_SENSITIVE, + get crypto_generichash() { + return sodium.crypto_generichash; + } + + get crypto_pwhash() { + return sodium.crypto_pwhash; + } + + get crypto_pwhash_ALG_ARGON2ID13() { + return sodium.crypto_pwhash_ALG_ARGON2ID13; + } + get crypto_pwhash_SALTBYTES() { + return sodium.crypto_pwhash_SALTBYTES; + } + get crypto_pwhash_ALG_ARGON2I13() { + return sodium.crypto_pwhash_ALG_ARGON2I13; + } + get crypto_pwhash_ALG_DEFAULT() { + return sodium.crypto_pwhash_ALG_DEFAULT; + } + get crypto_pwhash_OPSLIMIT_INTERACTIVE() { + return sodium.crypto_pwhash_OPSLIMIT_INTERACTIVE; + } + get crypto_pwhash_OPSLIMIT_MODERATE() { + return sodium.crypto_pwhash_OPSLIMIT_MODERATE; + } + get crypto_pwhash_OPSLIMIT_SENSITIVE() { + return sodium.crypto_pwhash_OPSLIMIT_SENSITIVE; + } + get crypto_pwhash_MEMLIMIT_INTERACTIVE() { + return sodium.crypto_pwhash_MEMLIMIT_INTERACTIVE; + } + get crypto_pwhash_MEMLIMIT_MODERATE() { + return sodium.crypto_pwhash_MEMLIMIT_MODERATE; + } + get crypto_pwhash_MEMLIMIT_SENSITIVE() { + return sodium.crypto_pwhash_MEMLIMIT_SENSITIVE; + } // helpers - from_base64, - to_base64, - randombytes_buf, - to_string, - from_hex, - base64_variants, + from_base64(input: string, variant?: base64_variants) { + return sodium.from_base64( + input, + variant ? convertVariant(variant) : undefined + ); + } + to_base64(input: string | Uint8Array, variant?: base64_variants): string { + return sodium.to_base64( + input, + variant ? convertVariant(variant) : undefined + ); + } + get randombytes_buf() { + return sodium.randombytes_buf; + } + get to_string() { + return sodium.to_string; + } + get from_hex() { + return sodium.from_hex; + } + + // aead + get crypto_aead_xchacha20poly1305_ietf_KEYBYTES() { + return sodium.crypto_aead_xchacha20poly1305_ietf_KEYBYTES; + } + get crypto_aead_xchacha20poly1305_ietf_encrypt() { + return sodium.crypto_aead_xchacha20poly1305_ietf_encrypt; + } + get crypto_aead_xchacha20poly1305_ietf_decrypt() { + return sodium.crypto_aead_xchacha20poly1305_ietf_decrypt; + } + get crypto_secretstream_xchacha20poly1305_init_push() { + return sodium.crypto_secretstream_xchacha20poly1305_init_push; + } + get crypto_secretstream_xchacha20poly1305_push() { + return sodium.crypto_secretstream_xchacha20poly1305_push; + } + get crypto_secretstream_xchacha20poly1305_init_pull() { + return sodium.crypto_secretstream_xchacha20poly1305_init_pull; + } + get crypto_secretstream_xchacha20poly1305_pull() { + return sodium.crypto_secretstream_xchacha20poly1305_pull; + } + get crypto_aead_xchacha20poly1305_ietf_NPUBBYTES() { + return sodium.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES; + } + get crypto_secretstream_xchacha20poly1305_TAG_FINAL() { + return sodium.crypto_secretstream_xchacha20poly1305_TAG_FINAL; + } + get crypto_secretstream_xchacha20poly1305_TAG_MESSAGE() { + return sodium.crypto_secretstream_xchacha20poly1305_TAG_MESSAGE; + } +} + +function convertVariant(variant: base64_variants): sodium.base64_variants { + switch (variant) { + case base64_variants.ORIGINAL: + return sodium.base64_variants.ORIGINAL; + case base64_variants.ORIGINAL_NO_PADDING: + return sodium.base64_variants.ORIGINAL_NO_PADDING; + case base64_variants.URLSAFE: + return sodium.base64_variants.URLSAFE; + case base64_variants.URLSAFE_NO_PADDING: + return sodium.base64_variants.URLSAFE_NO_PADDING; + } +} +export { base64_variants, ISodium }; +export { type StateAddress, type Uint8ArrayOutputFormat, type StringOutputFormat, - - // aead - crypto_aead_xchacha20poly1305_ietf_KEYBYTES, - crypto_aead_xchacha20poly1305_ietf_encrypt, - crypto_aead_xchacha20poly1305_ietf_decrypt, - crypto_secretstream_xchacha20poly1305_init_push, - crypto_secretstream_xchacha20poly1305_push, - crypto_secretstream_xchacha20poly1305_init_pull, - crypto_secretstream_xchacha20poly1305_pull, - crypto_aead_xchacha20poly1305_ietf_NPUBBYTES, - crypto_secretstream_xchacha20poly1305_TAG_FINAL, - crypto_secretstream_xchacha20poly1305_TAG_MESSAGE + type MessageTag, + type StringMessageTag } from "libsodium-wrappers-sumo"; diff --git a/packages/sodium/src/node.ts b/packages/sodium/src/node.ts index 224263311..9379b1548 100644 --- a/packages/sodium/src/node.ts +++ b/packages/sodium/src/node.ts @@ -32,18 +32,27 @@ import { crypto_secretstream_xchacha20poly1305_HEADERBYTES, crypto_secretstream_xchacha20poly1305_ABYTES, crypto_secretstream_xchacha20poly1305_STATEBYTES, - crypto_secretstream_xchacha20poly1305_TAGBYTES + crypto_secretstream_xchacha20poly1305_TAGBYTES, + crypto_pwhash_ALG_ARGON2ID13, + crypto_pwhash_SALTBYTES, + crypto_pwhash_ALG_ARGON2I13, + crypto_pwhash_ALG_DEFAULT, + crypto_pwhash_OPSLIMIT_INTERACTIVE, + crypto_pwhash_OPSLIMIT_MODERATE, + crypto_pwhash_OPSLIMIT_SENSITIVE, + crypto_pwhash_MEMLIMIT_INTERACTIVE, + crypto_pwhash_MEMLIMIT_MODERATE, + crypto_pwhash_MEMLIMIT_SENSITIVE, + crypto_aead_xchacha20poly1305_ietf_KEYBYTES, + crypto_aead_xchacha20poly1305_ietf_NPUBBYTES, + crypto_secretstream_xchacha20poly1305_TAG_FINAL, + crypto_secretstream_xchacha20poly1305_TAG_MESSAGE } from "sodium-native"; import { Buffer } from "node:buffer"; +import { base64_variants, ISodium } from "./types"; export type Uint8ArrayOutputFormat = "uint8array"; export type StringOutputFormat = "text" | "hex" | "base64"; -export enum base64_variants { - ORIGINAL = 1, - ORIGINAL_NO_PADDING = 3, - URLSAFE = 5, - URLSAFE_NO_PADDING = 7 -} export type StateAddress = { name: string }; export interface MessageTag { message: Uint8Array; @@ -54,9 +63,7 @@ export interface StringMessageTag { tag: number; } -export async function initialize() {} - -export function crypto_pwhash( +function crypto_pwhash( keyLength: number, password: string | Uint8Array, salt: Uint8Array, @@ -65,7 +72,7 @@ export function crypto_pwhash( algorithm: number, outputFormat?: Uint8ArrayOutputFormat | null ): Uint8Array; -export function crypto_pwhash( +function crypto_pwhash( keyLength: number, password: string | Uint8Array, salt: Uint8Array, @@ -74,7 +81,7 @@ export function crypto_pwhash( algorithm: number, outputFormat: StringOutputFormat ): string; -export function crypto_pwhash( +function crypto_pwhash( keyLength: number, password: string | Uint8Array, salt: Uint8Array, @@ -98,19 +105,19 @@ export function crypto_pwhash( ); } -export function crypto_generichash( +function crypto_generichash( hash_length: number, message: string | Uint8Array, key?: string | Uint8Array | null, outputFormat?: Uint8ArrayOutputFormat | null ): Uint8Array; -export function crypto_generichash( +function crypto_generichash( hash_length: number, message: string | Uint8Array, key: string | Uint8Array | null, outputFormat: StringOutputFormat ): string; -export function crypto_generichash( +function crypto_generichash( hash_length: number, message: string | Uint8Array, key?: string | Uint8Array | null, @@ -131,7 +138,7 @@ export function crypto_generichash( ); } -export function crypto_aead_xchacha20poly1305_ietf_encrypt( +function crypto_aead_xchacha20poly1305_ietf_encrypt( message: string | Uint8Array, additional_data: string | Uint8Array | null, secret_nonce: string | Uint8Array | null, @@ -139,7 +146,7 @@ export function crypto_aead_xchacha20poly1305_ietf_encrypt( key: Uint8Array, outputFormat?: Uint8ArrayOutputFormat | null ): Uint8Array; -export function crypto_aead_xchacha20poly1305_ietf_encrypt( +function crypto_aead_xchacha20poly1305_ietf_encrypt( message: string | Uint8Array, additional_data: string | Uint8Array | null, secret_nonce: string | Uint8Array | null, @@ -147,7 +154,7 @@ export function crypto_aead_xchacha20poly1305_ietf_encrypt( key: Uint8Array, outputFormat: StringOutputFormat ): string; -export function crypto_aead_xchacha20poly1305_ietf_encrypt( +function crypto_aead_xchacha20poly1305_ietf_encrypt( message: string | Uint8Array, additional_data: string | Uint8Array | null, secret_nonce: string | Uint8Array | null, @@ -171,15 +178,15 @@ export function crypto_aead_xchacha20poly1305_ietf_encrypt( ); } -export function crypto_secretstream_xchacha20poly1305_init_push( +function crypto_secretstream_xchacha20poly1305_init_push( key: Uint8Array, outputFormat?: Uint8ArrayOutputFormat | null ): { state: StateAddress; header: Uint8Array }; -export function crypto_secretstream_xchacha20poly1305_init_push( +function crypto_secretstream_xchacha20poly1305_init_push( key: Uint8Array, outputFormat: StringOutputFormat ): { state: StateAddress; header: string }; -export function crypto_secretstream_xchacha20poly1305_init_push( +function crypto_secretstream_xchacha20poly1305_init_push( key: Uint8Array, outputFormat?: StringOutputFormat | Uint8ArrayOutputFormat | null ): { state: StateAddress; header: string | Uint8Array } { @@ -198,21 +205,21 @@ export function crypto_secretstream_xchacha20poly1305_init_push( return { state: state as unknown as StateAddress, header }; } -export function crypto_secretstream_xchacha20poly1305_push( +function crypto_secretstream_xchacha20poly1305_push( state_address: StateAddress, message_chunk: string | Uint8Array, ad: string | Uint8Array | null, tag: number, outputFormat?: Uint8ArrayOutputFormat | null ): Uint8Array; -export function crypto_secretstream_xchacha20poly1305_push( +function crypto_secretstream_xchacha20poly1305_push( state_address: StateAddress, message_chunk: string | Uint8Array, ad: string | Uint8Array | null, tag: number, outputFormat: StringOutputFormat ): string; -export function crypto_secretstream_xchacha20poly1305_push( +function crypto_secretstream_xchacha20poly1305_push( state_address: StateAddress, message_chunk: string | Uint8Array, ad: string | Uint8Array | null, @@ -234,7 +241,7 @@ export function crypto_secretstream_xchacha20poly1305_push( ); } -export function crypto_aead_xchacha20poly1305_ietf_decrypt( +function crypto_aead_xchacha20poly1305_ietf_decrypt( secret_nonce: string | Uint8Array | null, ciphertext: string | Uint8Array, additional_data: string | Uint8Array | null, @@ -242,7 +249,7 @@ export function crypto_aead_xchacha20poly1305_ietf_decrypt( key: Uint8Array, outputFormat?: Uint8ArrayOutputFormat | null ): Uint8Array; -export function crypto_aead_xchacha20poly1305_ietf_decrypt( +function crypto_aead_xchacha20poly1305_ietf_decrypt( secret_nonce: string | Uint8Array | null, ciphertext: string | Uint8Array, additional_data: string | Uint8Array | null, @@ -250,7 +257,7 @@ export function crypto_aead_xchacha20poly1305_ietf_decrypt( key: Uint8Array, outputFormat: StringOutputFormat ): string; -export function crypto_aead_xchacha20poly1305_ietf_decrypt( +function crypto_aead_xchacha20poly1305_ietf_decrypt( _secret_nonce: string | Uint8Array | null, ciphertext: string | Uint8Array, additional_data: string | Uint8Array | null, @@ -274,7 +281,7 @@ export function crypto_aead_xchacha20poly1305_ietf_decrypt( ); } -export function crypto_secretstream_xchacha20poly1305_init_pull( +function crypto_secretstream_xchacha20poly1305_init_pull( header: Uint8Array, key: Uint8Array ): StateAddress { @@ -287,19 +294,19 @@ export function crypto_secretstream_xchacha20poly1305_init_pull( return state as unknown as StateAddress; } -export function crypto_secretstream_xchacha20poly1305_pull( +function crypto_secretstream_xchacha20poly1305_pull( state_address: StateAddress, cipher: string | Uint8Array, ad?: string | Uint8Array | null, outputFormat?: Uint8ArrayOutputFormat | null ): MessageTag; -export function crypto_secretstream_xchacha20poly1305_pull( +function crypto_secretstream_xchacha20poly1305_pull( state_address: StateAddress, cipher: string | Uint8Array, ad: string | Uint8Array | null, outputFormat: StringOutputFormat ): StringMessageTag; -export function crypto_secretstream_xchacha20poly1305_pull( +function crypto_secretstream_xchacha20poly1305_pull( state_address: StateAddress, ciphertext: string | Uint8Array, ad?: string | Uint8Array | null, @@ -322,15 +329,15 @@ export function crypto_secretstream_xchacha20poly1305_pull( return { message, tag: tag.readUInt8() } as MessageTag | StringMessageTag; } -export function randombytes_buf( +function randombytes_buf( length: number, outputFormat?: Uint8ArrayOutputFormat | null ): Uint8Array; -export function randombytes_buf( +function randombytes_buf( length: number, outputFormat: StringOutputFormat ): string; -export function randombytes_buf( +function randombytes_buf( length: number, outputFormat?: StringOutputFormat | Uint8ArrayOutputFormat | null ): string | Uint8Array { @@ -341,10 +348,7 @@ export function randombytes_buf( ); } -export function from_base64( - input: string, - variant?: base64_variants -): Uint8Array { +function from_base64(input: string, variant?: base64_variants): Uint8Array { return new Uint8Array( Buffer.from( variant === base64_variants.URLSAFE_NO_PADDING || @@ -359,7 +363,7 @@ export function from_base64( ); } -export function to_base64( +function to_base64( input: string | Uint8Array, variant: base64_variants = base64_variants.URLSAFE_NO_PADDING ): string { @@ -377,33 +381,16 @@ export function to_base64( : base64; } -export function from_hex(input: string): Uint8Array { +function from_hex(input: string): Uint8Array { return new Uint8Array(Buffer.from(input, "hex")); } -export function to_string(input: Uint8Array): string { +function to_string(input: Uint8Array): string { return Buffer.from(input, input.byteOffset, input.byteLength).toString( "utf-8" ); } -export { - crypto_pwhash_ALG_ARGON2ID13, - crypto_pwhash_SALTBYTES, - crypto_pwhash_ALG_ARGON2I13, - crypto_pwhash_ALG_DEFAULT, - crypto_pwhash_OPSLIMIT_INTERACTIVE, - crypto_pwhash_OPSLIMIT_MODERATE, - crypto_pwhash_OPSLIMIT_SENSITIVE, - crypto_pwhash_MEMLIMIT_INTERACTIVE, - crypto_pwhash_MEMLIMIT_MODERATE, - crypto_pwhash_MEMLIMIT_SENSITIVE, - crypto_aead_xchacha20poly1305_ietf_KEYBYTES, - crypto_aead_xchacha20poly1305_ietf_NPUBBYTES, - crypto_secretstream_xchacha20poly1305_TAG_FINAL, - crypto_secretstream_xchacha20poly1305_TAG_MESSAGE -} from "sodium-native"; - type ToBufferInput = string | Uint8Array | null | undefined; type ToBufferResult = TInput extends | undefined @@ -461,3 +448,97 @@ function trimPadding(str: string): string { } return str; } + +export class Sodium implements ISodium { + async initialize() {} + + get crypto_generichash() { + return crypto_generichash; + } + + get crypto_pwhash() { + return crypto_pwhash; + } + + get crypto_pwhash_ALG_ARGON2ID13() { + return crypto_pwhash_ALG_ARGON2ID13; + } + get crypto_pwhash_SALTBYTES() { + return crypto_pwhash_SALTBYTES; + } + get crypto_pwhash_ALG_ARGON2I13() { + return crypto_pwhash_ALG_ARGON2I13; + } + get crypto_pwhash_ALG_DEFAULT() { + return crypto_pwhash_ALG_DEFAULT; + } + get crypto_pwhash_OPSLIMIT_INTERACTIVE() { + return crypto_pwhash_OPSLIMIT_INTERACTIVE; + } + get crypto_pwhash_OPSLIMIT_MODERATE() { + return crypto_pwhash_OPSLIMIT_MODERATE; + } + get crypto_pwhash_OPSLIMIT_SENSITIVE() { + return crypto_pwhash_OPSLIMIT_SENSITIVE; + } + get crypto_pwhash_MEMLIMIT_INTERACTIVE() { + return crypto_pwhash_MEMLIMIT_INTERACTIVE; + } + get crypto_pwhash_MEMLIMIT_MODERATE() { + return crypto_pwhash_MEMLIMIT_MODERATE; + } + get crypto_pwhash_MEMLIMIT_SENSITIVE() { + return crypto_pwhash_MEMLIMIT_SENSITIVE; + } + + // helpers + get from_base64() { + return from_base64; + } + get to_base64() { + return to_base64; + } + get randombytes_buf() { + return randombytes_buf; + } + get to_string() { + return to_string; + } + get from_hex() { + return from_hex; + } + + // aead + get crypto_aead_xchacha20poly1305_ietf_KEYBYTES() { + return crypto_aead_xchacha20poly1305_ietf_KEYBYTES; + } + get crypto_aead_xchacha20poly1305_ietf_encrypt() { + return crypto_aead_xchacha20poly1305_ietf_encrypt; + } + get crypto_aead_xchacha20poly1305_ietf_decrypt() { + return crypto_aead_xchacha20poly1305_ietf_decrypt; + } + get crypto_secretstream_xchacha20poly1305_init_push() { + return crypto_secretstream_xchacha20poly1305_init_push; + } + get crypto_secretstream_xchacha20poly1305_push() { + return crypto_secretstream_xchacha20poly1305_push; + } + get crypto_secretstream_xchacha20poly1305_init_pull() { + return crypto_secretstream_xchacha20poly1305_init_pull; + } + get crypto_secretstream_xchacha20poly1305_pull() { + return crypto_secretstream_xchacha20poly1305_pull; + } + get crypto_aead_xchacha20poly1305_ietf_NPUBBYTES() { + return crypto_aead_xchacha20poly1305_ietf_NPUBBYTES; + } + get crypto_secretstream_xchacha20poly1305_TAG_FINAL() { + return crypto_secretstream_xchacha20poly1305_TAG_FINAL; + } + get crypto_secretstream_xchacha20poly1305_TAG_MESSAGE() { + return crypto_secretstream_xchacha20poly1305_TAG_MESSAGE; + } +} + +export { base64_variants, type ISodium }; diff --git a/packages/sodium/src/types.ts b/packages/sodium/src/types.ts new file mode 100644 index 000000000..7ff807104 --- /dev/null +++ b/packages/sodium/src/types.ts @@ -0,0 +1,64 @@ +/* +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 . +*/ + +import type sodium from "libsodium-wrappers-sumo"; + +export enum base64_variants { + ORIGINAL = 1, + ORIGINAL_NO_PADDING = 3, + URLSAFE = 5, + URLSAFE_NO_PADDING = 7 +} + +export interface ISodium { + initialize(): Promise; + + get crypto_generichash(): typeof sodium.crypto_generichash; + + get crypto_pwhash(): typeof sodium.crypto_pwhash; + get crypto_pwhash_ALG_ARGON2ID13(): typeof sodium.crypto_pwhash_ALG_ARGON2ID13; + get crypto_pwhash_SALTBYTES(): typeof sodium.crypto_pwhash_SALTBYTES; + get crypto_pwhash_ALG_ARGON2I13(): typeof sodium.crypto_pwhash_ALG_ARGON2I13; + get crypto_pwhash_ALG_DEFAULT(): typeof sodium.crypto_pwhash_ALG_DEFAULT; + get crypto_pwhash_OPSLIMIT_INTERACTIVE(): typeof sodium.crypto_pwhash_OPSLIMIT_INTERACTIVE; + get crypto_pwhash_OPSLIMIT_MODERATE(): typeof sodium.crypto_pwhash_OPSLIMIT_MODERATE; + get crypto_pwhash_OPSLIMIT_SENSITIVE(): typeof sodium.crypto_pwhash_OPSLIMIT_SENSITIVE; + get crypto_pwhash_MEMLIMIT_INTERACTIVE(): typeof sodium.crypto_pwhash_MEMLIMIT_INTERACTIVE; + get crypto_pwhash_MEMLIMIT_MODERATE(): typeof sodium.crypto_pwhash_MEMLIMIT_MODERATE; + get crypto_pwhash_MEMLIMIT_SENSITIVE(): typeof sodium.crypto_pwhash_MEMLIMIT_SENSITIVE; + + // helpers + from_base64(input: string, variant?: base64_variants): Uint8Array; + to_base64(input: string | Uint8Array, variant?: base64_variants): string; + get randombytes_buf(): typeof sodium.randombytes_buf; + get to_string(): typeof sodium.to_string; + get from_hex(): typeof sodium.from_hex; + + // aead + get crypto_aead_xchacha20poly1305_ietf_KEYBYTES(): typeof sodium.crypto_aead_xchacha20poly1305_ietf_KEYBYTES; + get crypto_aead_xchacha20poly1305_ietf_encrypt(): typeof sodium.crypto_aead_xchacha20poly1305_ietf_encrypt; + get crypto_aead_xchacha20poly1305_ietf_decrypt(): typeof sodium.crypto_aead_xchacha20poly1305_ietf_decrypt; + get crypto_secretstream_xchacha20poly1305_init_push(): typeof sodium.crypto_secretstream_xchacha20poly1305_init_push; + get crypto_secretstream_xchacha20poly1305_push(): typeof sodium.crypto_secretstream_xchacha20poly1305_push; + get crypto_secretstream_xchacha20poly1305_init_pull(): typeof sodium.crypto_secretstream_xchacha20poly1305_init_pull; + get crypto_secretstream_xchacha20poly1305_pull(): typeof sodium.crypto_secretstream_xchacha20poly1305_pull; + get crypto_aead_xchacha20poly1305_ietf_NPUBBYTES(): typeof sodium.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES; + get crypto_secretstream_xchacha20poly1305_TAG_FINAL(): typeof sodium.crypto_secretstream_xchacha20poly1305_TAG_FINAL; + get crypto_secretstream_xchacha20poly1305_TAG_MESSAGE(): typeof sodium.crypto_secretstream_xchacha20poly1305_TAG_MESSAGE; +} diff --git a/packages/sodium/tests/compat.test.ts b/packages/sodium/tests/compat.test.ts index e66d0c5e4..e1725089e 100644 --- a/packages/sodium/tests/compat.test.ts +++ b/packages/sodium/tests/compat.test.ts @@ -17,8 +17,9 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ -import * as browser from "../src/browser.js"; -import * as node from "../src/node.js"; +import { Sodium as BrowserSodium } from "../src/browser.ts"; +import { Sodium as NodeSodium } from "../src/node.ts"; +import { base64_variants } from "../src/types.ts"; import { test } from "vitest"; import { decrypt, @@ -27,7 +28,10 @@ import { hash, streamingDecrypt, streamingEncrypt -} from "./utils.js"; +} from "./utils.ts"; + +const browser = new BrowserSodium(); +const node = new NodeSodium(); test("secretstream tags should be equal on node & browser variants", async (t) => { t.expect(browser.crypto_secretstream_xchacha20poly1305_TAG_FINAL).toBe( @@ -100,19 +104,19 @@ test("node & browser variants of base64 should be compatible", async (t) => { t.expect(browser.to_base64(text)).toBe(node.to_base64(text)); - t.expect(browser.to_base64(text, browser.base64_variants.ORIGINAL)).toBe( - node.to_base64(text, node.base64_variants.ORIGINAL) + t.expect(browser.to_base64(text, base64_variants.ORIGINAL)).toBe( + node.to_base64(text, base64_variants.ORIGINAL) ); - t.expect( - browser.to_base64(text, browser.base64_variants.ORIGINAL_NO_PADDING) - ).toBe(node.to_base64(text, node.base64_variants.ORIGINAL_NO_PADDING)); + t.expect(browser.to_base64(text, base64_variants.ORIGINAL_NO_PADDING)).toBe( + node.to_base64(text, base64_variants.ORIGINAL_NO_PADDING) + ); - t.expect( - browser.to_base64(text, browser.base64_variants.URLSAFE_NO_PADDING) - ).toBe(node.to_base64(text, node.base64_variants.URLSAFE_NO_PADDING)); + t.expect(browser.to_base64(text, base64_variants.URLSAFE_NO_PADDING)).toBe( + node.to_base64(text, base64_variants.URLSAFE_NO_PADDING) + ); - t.expect(browser.to_base64(text, browser.base64_variants.URLSAFE)).toBe( - node.to_base64(text, node.base64_variants.URLSAFE) + t.expect(browser.to_base64(text, base64_variants.URLSAFE)).toBe( + node.to_base64(text, base64_variants.URLSAFE) ); }); diff --git a/packages/sodium/tests/utils.ts b/packages/sodium/tests/utils.ts index 211127edb..696641714 100644 --- a/packages/sodium/tests/utils.ts +++ b/packages/sodium/tests/utils.ts @@ -16,13 +16,12 @@ 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 . */ -import * as browser from "../src/browser.js"; -import * as node from "../src/node.js"; +// import * as browser from "../src/browser.js"; +// import * as node from "../src/node.js"; -export async function streamingEncrypt( - crypto: typeof node | typeof browser, - key: Uint8Array -) { +import { ISodium } from "../src/types"; + +export async function streamingEncrypt(crypto: ISodium, key: Uint8Array) { await crypto.initialize(); const { state, header } = crypto.crypto_secretstream_xchacha20poly1305_init_push(key, "base64"); @@ -56,7 +55,7 @@ export async function streamingEncrypt( } export async function streamingDecrypt( - crypto: typeof node | typeof browser, + crypto: ISodium, key: Uint8Array, cipher: { header: string; chunks: string[] } ) { @@ -88,7 +87,7 @@ export async function streamingDecrypt( } export async function decrypt( - crypto: typeof node | typeof browser, + crypto: ISodium, cipher: Uint8Array, nonce: Uint8Array, key: Uint8Array @@ -105,7 +104,7 @@ export async function decrypt( } export async function encrypt( - crypto: typeof node | typeof browser, + crypto: ISodium, nonce: Uint8Array, key: Uint8Array ) { @@ -120,7 +119,7 @@ export async function encrypt( ); } -export async function getKey(crypto: typeof node | typeof browser) { +export async function getKey(crypto: ISodium) { await crypto.initialize(); const saltBytes: Uint8Array = crypto.randombytes_buf( @@ -137,10 +136,10 @@ export async function getKey(crypto: typeof node | typeof browser) { return { key, salt: saltBytes }; } -export async function hash(crypto: typeof node | typeof browser) { +export async function hash(crypto: ISodium) { await crypto.initialize(); const saltBytes = crypto.crypto_generichash( - node.crypto_pwhash_SALTBYTES, + crypto.crypto_pwhash_SALTBYTES, "mysalt" ); return crypto.crypto_pwhash( @@ -149,7 +148,7 @@ export async function hash(crypto: typeof node | typeof browser) { saltBytes, 3, // operations limit 1024 * 1024 * 64, // memory limit (8MB) - browser.crypto_pwhash_ALG_ARGON2ID13, + crypto.crypto_pwhash_ALG_ARGON2ID13, "base64" ); }