mirror of
https://github.com/denoland/deno.git
synced 2024-11-28 16:20:57 -05:00
refactor(node/crypto): scrypt polyfill to rust (#18746)
This commit is contained in:
parent
8176b18a5e
commit
6c928a7d27
6 changed files with 167 additions and 179 deletions
33
Cargo.lock
generated
33
Cargo.lock
generated
|
@ -1171,6 +1171,7 @@ dependencies = [
|
|||
"regex",
|
||||
"ripemd",
|
||||
"rsa",
|
||||
"scrypt",
|
||||
"serde",
|
||||
"sha-1 0.10.0",
|
||||
"sha2",
|
||||
|
@ -3157,6 +3158,17 @@ dependencies = [
|
|||
"windows-sys 0.45.0",
|
||||
]
|
||||
|
||||
[[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"
|
||||
|
@ -3821,6 +3833,15 @@ version = "1.0.13"
|
|||
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"
|
||||
|
@ -3851,6 +3872,18 @@ version = "1.1.0"
|
|||
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"
|
||||
|
|
|
@ -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", () => {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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, ¶ms, 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?
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 buf = Buffer.alloc(keylen);
|
||||
ops.op_node_scrypt_sync(
|
||||
password,
|
||||
salt,
|
||||
keylen,
|
||||
Math.log2(N),
|
||||
r,
|
||||
p,
|
||||
maxmem,
|
||||
buf.buffer,
|
||||
);
|
||||
|
||||
const scryptRom = new ScryptRom(b, r, N, p);
|
||||
const out = scryptRom.run();
|
||||
|
||||
const fin = pbkdf2(password, out, 1, keylen, "sha256");
|
||||
scryptRom.clean();
|
||||
return fin;
|
||||
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);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue