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",
|
"regex",
|
||||||
"ripemd",
|
"ripemd",
|
||||||
"rsa",
|
"rsa",
|
||||||
|
"scrypt",
|
||||||
"serde",
|
"serde",
|
||||||
"sha-1 0.10.0",
|
"sha-1 0.10.0",
|
||||||
"sha2",
|
"sha2",
|
||||||
|
@ -3157,6 +3158,17 @@ dependencies = [
|
||||||
"windows-sys 0.45.0",
|
"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]]
|
[[package]]
|
||||||
name = "path-clean"
|
name = "path-clean"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
@ -3821,6 +3833,15 @@ version = "1.0.13"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041"
|
checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "salsa20"
|
||||||
|
version = "0.10.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213"
|
||||||
|
dependencies = [
|
||||||
|
"cipher",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "same-file"
|
name = "same-file"
|
||||||
version = "1.0.6"
|
version = "1.0.6"
|
||||||
|
@ -3851,6 +3872,18 @@ version = "1.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
|
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]]
|
[[package]]
|
||||||
name = "sct"
|
name = "sct"
|
||||||
version = "0.7.0"
|
version = "0.7.0"
|
||||||
|
|
|
@ -2,8 +2,11 @@
|
||||||
import { scrypt, scryptSync } from "node:crypto";
|
import { scrypt, scryptSync } from "node:crypto";
|
||||||
import { Buffer } from "node:buffer";
|
import { Buffer } from "node:buffer";
|
||||||
import { assertEquals } from "../../../../test_util/std/testing/asserts.ts";
|
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) => {
|
scrypt("password", "salt", 32, (err, key) => {
|
||||||
if (err) throw err;
|
if (err) throw err;
|
||||||
assertEquals(
|
assertEquals(
|
||||||
|
@ -43,10 +46,15 @@ Deno.test("scrypt works correctly", () => {
|
||||||
115,
|
115,
|
||||||
]),
|
]),
|
||||||
);
|
);
|
||||||
|
promise.resolve(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await promise;
|
||||||
});
|
});
|
||||||
|
|
||||||
Deno.test("scrypt works with options", () => {
|
Deno.test("scrypt works with options", async () => {
|
||||||
|
const promise = deferred();
|
||||||
|
|
||||||
scrypt(
|
scrypt(
|
||||||
"password",
|
"password",
|
||||||
"salt",
|
"salt",
|
||||||
|
@ -93,8 +101,11 @@ Deno.test("scrypt works with options", () => {
|
||||||
71,
|
71,
|
||||||
]),
|
]),
|
||||||
);
|
);
|
||||||
|
promise.resolve(true);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
await promise;
|
||||||
});
|
});
|
||||||
|
|
||||||
Deno.test("scryptSync works correctly", () => {
|
Deno.test("scryptSync works correctly", () => {
|
||||||
|
|
|
@ -38,6 +38,7 @@ rand.workspace = true
|
||||||
regex.workspace = true
|
regex.workspace = true
|
||||||
ripemd = "0.1.3"
|
ripemd = "0.1.3"
|
||||||
rsa.workspace = true
|
rsa.workspace = true
|
||||||
|
scrypt = "0.11.0"
|
||||||
serde = "1.0.149"
|
serde = "1.0.149"
|
||||||
sha-1 = "0.10.0"
|
sha-1 = "0.10.0"
|
||||||
sha2.workspace = true
|
sha2.workspace = true
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
// 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::type_error;
|
||||||
use deno_core::error::AnyError;
|
use deno_core::error::AnyError;
|
||||||
use deno_core::op;
|
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))
|
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_sign,
|
||||||
crypto::op_node_verify,
|
crypto::op_node_verify,
|
||||||
crypto::op_node_random_int,
|
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_parse,
|
||||||
crypto::x509::op_node_x509_ca,
|
crypto::x509::op_node_x509_ca,
|
||||||
crypto::x509::op_node_x509_check_email,
|
crypto::x509::op_node_x509_check_email,
|
||||||
|
|
|
@ -24,9 +24,11 @@ SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Buffer } from "ext:deno_node/buffer.ts";
|
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";
|
import { HASH_DATA } from "ext:deno_node/internal/crypto/types.ts";
|
||||||
|
|
||||||
|
const { core } = globalThis.__bootstrap;
|
||||||
|
const { ops } = core;
|
||||||
|
|
||||||
type Opts = Partial<{
|
type Opts = Partial<{
|
||||||
N: number;
|
N: number;
|
||||||
cost: number;
|
cost: number;
|
||||||
|
@ -55,166 +57,6 @@ const fixOpts = (opts?: Opts) => {
|
||||||
return out;
|
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(
|
export function scryptSync(
|
||||||
password: HASH_DATA,
|
password: HASH_DATA,
|
||||||
salt: HASH_DATA,
|
salt: HASH_DATA,
|
||||||
|
@ -226,17 +68,22 @@ export function scryptSync(
|
||||||
const blen = p * 128 * r;
|
const blen = p * 128 * r;
|
||||||
|
|
||||||
if (32 * r * (N + 2) * 4 + blen > maxmem) {
|
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);
|
return buf;
|
||||||
const out = scryptRom.run();
|
|
||||||
|
|
||||||
const fin = pbkdf2(password, out, 1, keylen, "sha256");
|
|
||||||
scryptRom.clean();
|
|
||||||
return fin;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Callback = (err: unknown, result?: Buffer) => void;
|
type Callback = (err: unknown, result?: Buffer) => void;
|
||||||
|
@ -256,17 +103,24 @@ export function scrypt(
|
||||||
|
|
||||||
const blen = p * 128 * r;
|
const blen = p * 128 * r;
|
||||||
if (32 * r * (N + 2) * 4 + blen > maxmem) {
|
if (32 * r * (N + 2) * 4 + blen > maxmem) {
|
||||||
throw new Error("excedes max memory");
|
throw new Error("exceeds max memory");
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const b = pbkdf2(password, salt, 1, blen, "sha256");
|
core.opAsync(
|
||||||
|
"op_node_scrypt_async",
|
||||||
const scryptRom = new ScryptRom(b, r, N, p);
|
password,
|
||||||
const out = scryptRom.run();
|
salt,
|
||||||
const result = pbkdf2(password, out, 1, keylen, "sha256");
|
keylen,
|
||||||
scryptRom.clean();
|
Math.log2(N),
|
||||||
cb(null, result);
|
r,
|
||||||
|
p,
|
||||||
|
maxmem,
|
||||||
|
).then(
|
||||||
|
(buf: Uint8Array) => {
|
||||||
|
cb(null, Buffer.from(buf.buffer));
|
||||||
|
},
|
||||||
|
);
|
||||||
} catch (err: unknown) {
|
} catch (err: unknown) {
|
||||||
return cb(err);
|
return cb(err);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue