summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLevente Kurusa <lkurusa@kernelstuff.org>2023-04-18 14:29:10 +0200
committerGitHub <noreply@github.com>2023-04-18 14:29:10 +0200
commit530963c34c5cc5260b07029fff6c4ae85f52d830 (patch)
tree2eb88362f222610f9bd934e7627d8928e630cc98
parent74aee8b3058f1c32f367fced8f8a33e7d0aff38c (diff)
refactor(node/crypto): scrypt polyfill to rust (#18746)
-rw-r--r--Cargo.lock33
-rw-r--r--cli/tests/unit_node/internal/scrypt_test.ts15
-rw-r--r--ext/node/Cargo.toml1
-rw-r--r--ext/node/crypto/mod.rs87
-rw-r--r--ext/node/lib.rs2
-rw-r--r--ext/node/polyfills/internal/crypto/scrypt.ts210
6 files changed, 168 insertions, 180 deletions
diff --git a/Cargo.lock b/Cargo.lock
index a06d47c80..021cec57b 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1171,6 +1171,7 @@ dependencies = [
"regex",
"ripemd",
"rsa",
+ "scrypt",
"serde",
"sha-1 0.10.0",
"sha2",
@@ -3158,6 +3159,17 @@ dependencies = [
]
[[package]]
+name = "password-hash"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166"
+dependencies = [
+ "base64ct",
+ "rand_core 0.6.4",
+ "subtle",
+]
+
+[[package]]
name = "path-clean"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3822,6 +3834,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041"
[[package]]
+name = "salsa20"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213"
+dependencies = [
+ "cipher",
+]
+
+[[package]]
name = "same-file"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3852,6 +3873,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
+name = "scrypt"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0516a385866c09368f0b5bcd1caff3366aace790fcd46e2bb032697bb172fd1f"
+dependencies = [
+ "password-hash",
+ "pbkdf2",
+ "salsa20",
+ "sha2",
+]
+
+[[package]]
name = "sct"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/cli/tests/unit_node/internal/scrypt_test.ts b/cli/tests/unit_node/internal/scrypt_test.ts
index e98a1a3af..d65cd27ff 100644
--- a/cli/tests/unit_node/internal/scrypt_test.ts
+++ b/cli/tests/unit_node/internal/scrypt_test.ts
@@ -2,8 +2,11 @@
import { scrypt, scryptSync } from "node:crypto";
import { Buffer } from "node:buffer";
import { assertEquals } from "../../../../test_util/std/testing/asserts.ts";
+import { deferred } from "../../../../test_util/std/async/deferred.ts";
+
+Deno.test("scrypt works correctly", async () => {
+ const promise = deferred();
-Deno.test("scrypt works correctly", () => {
scrypt("password", "salt", 32, (err, key) => {
if (err) throw err;
assertEquals(
@@ -43,10 +46,15 @@ Deno.test("scrypt works correctly", () => {
115,
]),
);
+ promise.resolve(true);
});
+
+ await promise;
});
-Deno.test("scrypt works with options", () => {
+Deno.test("scrypt works with options", async () => {
+ const promise = deferred();
+
scrypt(
"password",
"salt",
@@ -93,8 +101,11 @@ Deno.test("scrypt works with options", () => {
71,
]),
);
+ promise.resolve(true);
},
);
+
+ await promise;
});
Deno.test("scryptSync works correctly", () => {
diff --git a/ext/node/Cargo.toml b/ext/node/Cargo.toml
index 75d85ac0b..76424daa0 100644
--- a/ext/node/Cargo.toml
+++ b/ext/node/Cargo.toml
@@ -38,6 +38,7 @@ rand.workspace = true
regex.workspace = true
ripemd = "0.1.3"
rsa.workspace = true
+scrypt = "0.11.0"
serde = "1.0.149"
sha-1 = "0.10.0"
sha2.workspace = true
diff --git a/ext/node/crypto/mod.rs b/ext/node/crypto/mod.rs
index b45f36144..26392da4c 100644
--- a/ext/node/crypto/mod.rs
+++ b/ext/node/crypto/mod.rs
@@ -1,4 +1,5 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+use deno_core::error::generic_error;
use deno_core::error::type_error;
use deno_core::error::AnyError;
use deno_core::op;
@@ -542,3 +543,89 @@ pub fn op_node_random_int(min: i32, max: i32) -> Result<i32, AnyError> {
Ok(dist.sample(&mut rng))
}
+
+#[allow(clippy::too_many_arguments)]
+fn scrypt(
+ password: StringOrBuffer,
+ salt: StringOrBuffer,
+ keylen: u32,
+ cost: u32,
+ block_size: u32,
+ parallelization: u32,
+ _maxmem: u32,
+ output_buffer: &mut [u8],
+) -> Result<(), AnyError> {
+ // Construct Params
+ let params = scrypt::Params::new(
+ cost as u8,
+ block_size,
+ parallelization,
+ keylen as usize,
+ )
+ .unwrap();
+
+ // Call into scrypt
+ let res = scrypt::scrypt(&password, &salt, &params, output_buffer);
+ if res.is_ok() {
+ Ok(())
+ } else {
+ // TODO(lev): key derivation failed, so what?
+ Err(generic_error("scrypt key derivation failed"))
+ }
+}
+
+#[op]
+pub fn op_node_scrypt_sync(
+ password: StringOrBuffer,
+ salt: StringOrBuffer,
+ keylen: u32,
+ cost: u32,
+ block_size: u32,
+ parallelization: u32,
+ maxmem: u32,
+ output_buffer: &mut [u8],
+) -> Result<(), AnyError> {
+ scrypt(
+ password,
+ salt,
+ keylen,
+ cost,
+ block_size,
+ parallelization,
+ maxmem,
+ output_buffer,
+ )
+}
+
+#[op]
+pub async fn op_node_scrypt_async(
+ password: StringOrBuffer,
+ salt: StringOrBuffer,
+ keylen: u32,
+ cost: u32,
+ block_size: u32,
+ parallelization: u32,
+ maxmem: u32,
+) -> Result<ZeroCopyBuf, AnyError> {
+ tokio::task::spawn_blocking(move || {
+ let mut output_buffer = vec![0u8; keylen as usize];
+ let res = scrypt(
+ password,
+ salt,
+ keylen,
+ cost,
+ block_size,
+ parallelization,
+ maxmem,
+ &mut output_buffer,
+ );
+
+ if res.is_ok() {
+ Ok(output_buffer.into())
+ } else {
+ // TODO(lev): rethrow the error?
+ Err(generic_error("scrypt failure"))
+ }
+ })
+ .await?
+}
diff --git a/ext/node/lib.rs b/ext/node/lib.rs
index b09cb1c90..d363c444a 100644
--- a/ext/node/lib.rs
+++ b/ext/node/lib.rs
@@ -196,6 +196,8 @@ deno_core::extension!(deno_node,
crypto::op_node_sign,
crypto::op_node_verify,
crypto::op_node_random_int,
+ crypto::op_node_scrypt_sync,
+ crypto::op_node_scrypt_async,
crypto::x509::op_node_x509_parse,
crypto::x509::op_node_x509_ca,
crypto::x509::op_node_x509_check_email,
diff --git a/ext/node/polyfills/internal/crypto/scrypt.ts b/ext/node/polyfills/internal/crypto/scrypt.ts
index 5e6586906..438335531 100644
--- a/ext/node/polyfills/internal/crypto/scrypt.ts
+++ b/ext/node/polyfills/internal/crypto/scrypt.ts
@@ -24,9 +24,11 @@ SOFTWARE.
*/
import { Buffer } from "ext:deno_node/buffer.ts";
-import { pbkdf2Sync as pbkdf2 } from "ext:deno_node/internal/crypto/pbkdf2.ts";
import { HASH_DATA } from "ext:deno_node/internal/crypto/types.ts";
+const { core } = globalThis.__bootstrap;
+const { ops } = core;
+
type Opts = Partial<{
N: number;
cost: number;
@@ -55,166 +57,6 @@ const fixOpts = (opts?: Opts) => {
return out;
};
-function blockxor(S: Buffer, Si: number, D: Buffer, Di: number, len: number) {
- let i = -1;
- while (++i < len) D[Di + i] ^= S[Si + i];
-}
-function arraycopy(
- src: Buffer,
- srcPos: number,
- dest: Buffer,
- destPos: number,
- length: number,
-) {
- src.copy(dest, destPos, srcPos, srcPos + length);
-}
-
-const R = (a: number, b: number) => (a << b) | (a >>> (32 - b));
-
-class ScryptRom {
- B: Buffer;
- r: number;
- N: number;
- p: number;
- XY: Buffer;
- V: Buffer;
- B32: Int32Array;
- x: Int32Array;
- _X: Buffer;
- constructor(b: Buffer, r: number, N: number, p: number) {
- this.B = b;
- this.r = r;
- this.N = N;
- this.p = p;
- this.XY = Buffer.allocUnsafe(256 * r);
- this.V = Buffer.allocUnsafe(128 * r * N);
- this.B32 = new Int32Array(16); // salsa20_8
- this.x = new Int32Array(16); // salsa20_8
- this._X = Buffer.allocUnsafe(64); // blockmix_salsa8
- }
-
- run() {
- const p = this.p | 0;
- const r = this.r | 0;
- for (let i = 0; i < p; i++) this.scryptROMix(i, r);
-
- return this.B;
- }
-
- scryptROMix(i: number, r: number) {
- const blockStart = i * 128 * r;
- const offset = (2 * r - 1) * 64;
- const blockLen = 128 * r;
- const B = this.B;
- const N = this.N | 0;
- const V = this.V;
- const XY = this.XY;
- B.copy(XY, 0, blockStart, blockStart + blockLen);
- for (let i1 = 0; i1 < N; i1++) {
- XY.copy(V, i1 * blockLen, 0, blockLen);
- this.blockmix_salsa8(blockLen);
- }
-
- let j: number;
- for (let i2 = 0; i2 < N; i2++) {
- j = XY.readUInt32LE(offset) & (N - 1);
- blockxor(V, j * blockLen, XY, 0, blockLen);
- this.blockmix_salsa8(blockLen);
- }
- XY.copy(B, blockStart, 0, blockLen);
- }
-
- blockmix_salsa8(blockLen: number) {
- const BY = this.XY;
- const r = this.r;
- const _X = this._X;
- arraycopy(BY, (2 * r - 1) * 64, _X, 0, 64);
- let i;
- for (i = 0; i < 2 * r; i++) {
- blockxor(BY, i * 64, _X, 0, 64);
- this.salsa20_8();
- arraycopy(_X, 0, BY, blockLen + i * 64, 64);
- }
- for (i = 0; i < r; i++) {
- arraycopy(BY, blockLen + i * 2 * 64, BY, i * 64, 64);
- arraycopy(BY, blockLen + (i * 2 + 1) * 64, BY, (i + r) * 64, 64);
- }
- }
-
- salsa20_8() {
- const B32 = this.B32;
- const B = this._X;
- const x = this.x;
-
- let i;
- for (i = 0; i < 16; i++) {
- B32[i] = (B[i * 4 + 0] & 0xff) << 0;
- B32[i] |= (B[i * 4 + 1] & 0xff) << 8;
- B32[i] |= (B[i * 4 + 2] & 0xff) << 16;
- B32[i] |= (B[i * 4 + 3] & 0xff) << 24;
- }
-
- for (i = 0; i < 16; i++) x[i] = B32[i];
-
- for (i = 0; i < 4; i++) {
- x[4] ^= R(x[0] + x[12], 7);
- x[8] ^= R(x[4] + x[0], 9);
- x[12] ^= R(x[8] + x[4], 13);
- x[0] ^= R(x[12] + x[8], 18);
- x[9] ^= R(x[5] + x[1], 7);
- x[13] ^= R(x[9] + x[5], 9);
- x[1] ^= R(x[13] + x[9], 13);
- x[5] ^= R(x[1] + x[13], 18);
- x[14] ^= R(x[10] + x[6], 7);
- x[2] ^= R(x[14] + x[10], 9);
- x[6] ^= R(x[2] + x[14], 13);
- x[10] ^= R(x[6] + x[2], 18);
- x[3] ^= R(x[15] + x[11], 7);
- x[7] ^= R(x[3] + x[15], 9);
- x[11] ^= R(x[7] + x[3], 13);
- x[15] ^= R(x[11] + x[7], 18);
- x[1] ^= R(x[0] + x[3], 7);
- x[2] ^= R(x[1] + x[0], 9);
- x[3] ^= R(x[2] + x[1], 13);
- x[0] ^= R(x[3] + x[2], 18);
- x[6] ^= R(x[5] + x[4], 7);
- x[7] ^= R(x[6] + x[5], 9);
- x[4] ^= R(x[7] + x[6], 13);
- x[5] ^= R(x[4] + x[7], 18);
- x[11] ^= R(x[10] + x[9], 7);
- x[8] ^= R(x[11] + x[10], 9);
- x[9] ^= R(x[8] + x[11], 13);
- x[10] ^= R(x[9] + x[8], 18);
- x[12] ^= R(x[15] + x[14], 7);
- x[13] ^= R(x[12] + x[15], 9);
- x[14] ^= R(x[13] + x[12], 13);
- x[15] ^= R(x[14] + x[13], 18);
- }
- for (i = 0; i < 16; i++) B32[i] += x[i];
-
- let bi;
-
- for (i = 0; i < 16; i++) {
- bi = i * 4;
- B[bi + 0] = (B32[i] >> 0) & 0xff;
- B[bi + 1] = (B32[i] >> 8) & 0xff;
- B[bi + 2] = (B32[i] >> 16) & 0xff;
- B[bi + 3] = (B32[i] >> 24) & 0xff;
- }
- }
-
- clean() {
- this.XY.fill(0);
- this.V.fill(0);
- this._X.fill(0);
- this.B.fill(0);
- for (let i = 0; i < 16; i++) {
- this.B32[i] = 0;
- this.x[i] = 0;
- }
- }
-}
-
export function scryptSync(
password: HASH_DATA,
salt: HASH_DATA,
@@ -226,17 +68,22 @@ export function scryptSync(
const blen = p * 128 * r;
if (32 * r * (N + 2) * 4 + blen > maxmem) {
- throw new Error("excedes max memory");
+ throw new Error("exceeds max memory");
}
- const b = pbkdf2(password, salt, 1, blen, "sha256");
-
- const scryptRom = new ScryptRom(b, r, N, p);
- const out = scryptRom.run();
-
- const fin = pbkdf2(password, out, 1, keylen, "sha256");
- scryptRom.clean();
- return fin;
+ const buf = Buffer.alloc(keylen);
+ ops.op_node_scrypt_sync(
+ password,
+ salt,
+ keylen,
+ Math.log2(N),
+ r,
+ p,
+ maxmem,
+ buf.buffer,
+ );
+
+ return buf;
}
type Callback = (err: unknown, result?: Buffer) => void;
@@ -256,17 +103,24 @@ export function scrypt(
const blen = p * 128 * r;
if (32 * r * (N + 2) * 4 + blen > maxmem) {
- throw new Error("excedes max memory");
+ throw new Error("exceeds max memory");
}
try {
- const b = pbkdf2(password, salt, 1, blen, "sha256");
-
- const scryptRom = new ScryptRom(b, r, N, p);
- const out = scryptRom.run();
- const result = pbkdf2(password, out, 1, keylen, "sha256");
- scryptRom.clean();
- cb(null, result);
+ core.opAsync(
+ "op_node_scrypt_async",
+ password,
+ salt,
+ keylen,
+ Math.log2(N),
+ r,
+ p,
+ maxmem,
+ ).then(
+ (buf: Uint8Array) => {
+ cb(null, Buffer.from(buf.buffer));
+ },
+ );
} catch (err: unknown) {
return cb(err);
}