crypto: @notesnook/sodium no longer requires top-level await

This commit is contained in:
Abdullah Atta
2024-09-23 19:30:18 +05:00
parent 69468487c2
commit 7e355c3ecb
11 changed files with 441 additions and 297 deletions

View File

@@ -17,28 +17,21 @@ 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 {
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<DataFormat>): Uint8Array {
private static transformInput(
sodium: ISodium,
cipherData: Cipher<DataFormat>
): 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<TOutputFormat extends DataFormat>(
sodium: ISodium,
key: SerializedKey,
cipherData: Cipher<DataFormat>,
outputFormat: TOutputFormat = "text" as TOutputFormat
): Output<TOutputFormat> {
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<TOutputFormat>;
}
static createStream(
sodium: ISodium,
header: string,
key: SerializedKey
): TransformStream<Uint8Array, Uint8Array> {
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<Uint8Array, Uint8Array>({
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();
}
});

View File

@@ -17,30 +17,20 @@ 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 {
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<DataFormat>,
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<TOutputFormat extends DataFormat>(
sodium: ISodium,
key: SerializedKey,
input: Input<DataFormat>,
format: DataFormat,
outputFormat: TOutputFormat = "uint8array" as TOutputFormat
): Cipher<TOutputFormat> {
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<TOutputFormat>;
}
static createStream(key: SerializedKey): {
static createStream(
sodium: ISodium,
key: SerializedKey
): {
iv: string;
stream: TransformStream<Chunk, Uint8Array>;
} {
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}`;

View File

@@ -17,7 +17,7 @@ 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 { 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<Cipher<TOutputFormat>> {
await this.init();
return Encryption.encrypt(
this.sodium,
key,
input,
format,
@@ -64,7 +66,7 @@ export class NNCrypto implements INNCrypto {
): Promise<Cipher<TOutputFormat>[]> {
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<Output<TOutputFormat>> {
await this.init();
return Decryption.decrypt(key, cipherData, outputFormat);
return Decryption.decrypt(this.sodium, key, cipherData, outputFormat);
}
async decryptMulti<TOutputFormat extends DataFormat>(
@@ -85,88 +87,37 @@ export class NNCrypto implements INNCrypto {
await this.init();
const decryptedItems: Output<TOutputFormat>[] = [];
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<string> {
await this.init();
return Password.hash(password, salt);
return Password.hash(this.sodium, password, salt);
}
async deriveKey(password: string, salt?: string): Promise<EncryptionKey> {
await this.init();
return KeyUtils.deriveKey(password, salt);
return KeyUtils.deriveKey(this.sodium, password, salt);
}
async exportKey(password: string, salt?: string): Promise<SerializedKey> {
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<string> {
// await this.init();
// return await this.createEncryptionStream(key, stream);
// }
// async decryptStream(
// key: SerializedKey,
// iv: string,
// stream: IStreamable,
// _streamId?: string
// ): Promise<void> {
// await this.init();
// await this.createDecryptionStream(iv, key, stream);
// }
}
export * from "./types.js";

View File

@@ -17,46 +17,47 @@ 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 {
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.");
}

View File

@@ -17,23 +17,21 @@ 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 {
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;

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
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();

View File

@@ -18,45 +18,125 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
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";

View File

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

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
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<void>;
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;
}

View File

@@ -17,8 +17,9 @@ 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 * 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)
);
});

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
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"
);
}