1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-11-30 16:40:57 -05:00
denoland-deno/ext/node/polyfills/internal/crypto/pbkdf2.ts
Luca Casonato 08e5606c34
fix(ext/node): rewrite digest handling (#24392)
Previously we had many different code paths all
handling digests in different places, all with
wildly different digest support. This commit
rewrites this to use a single digest handling
mechanism for all digest operations.

It adds various aliases for digest algorithms,
like node does. For example
`sha1WithRSAEncryption` is an alias for `sha1`.

It also adds support for `md5-sha1` digests in
various places.
2024-07-05 10:10:22 +02:00

143 lines
3.3 KiB
TypeScript

// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
// TODO(petamoriken): enable prefer-primordials for node polyfills
// deno-lint-ignore-file prefer-primordials
import { op_node_pbkdf2, op_node_pbkdf2_async } from "ext:core/ops";
import { Buffer } from "node:buffer";
import { HASH_DATA } from "ext:deno_node/internal/crypto/types.ts";
import {
validateFunction,
validateString,
validateUint32,
} from "ext:deno_node/internal/validators.mjs";
import { getArrayBufferOrView } from "ext:deno_node/internal/crypto/keys.ts";
import {
ERR_CRYPTO_INVALID_DIGEST,
ERR_OUT_OF_RANGE,
} from "ext:deno_node/internal/errors.ts";
export const MAX_ALLOC = Math.pow(2, 30) - 1;
export const MAX_I32 = 2 ** 31 - 1;
export type NormalizedAlgorithms =
| "md5"
| "ripemd160"
| "sha1"
| "sha224"
| "sha256"
| "sha384"
| "sha512";
export type Algorithms =
| "md5"
| "ripemd160"
| "rmd160"
| "sha1"
| "sha224"
| "sha256"
| "sha384"
| "sha512";
function check(
password: HASH_DATA,
salt: HASH_DATA,
iterations: number,
keylen: number,
digest: string,
) {
validateString(digest, "digest");
password = getArrayBufferOrView(password, "password", "buffer");
salt = getArrayBufferOrView(salt, "salt", "buffer");
validateUint32(iterations, "iterations", true);
validateUint32(keylen, "keylen");
if (iterations > MAX_I32) {
throw new ERR_OUT_OF_RANGE("iterations", `<= ${MAX_I32}`, iterations);
}
if (keylen > MAX_I32) {
throw new ERR_OUT_OF_RANGE("keylen", `<= ${MAX_I32}`, keylen);
}
return { password, salt, iterations, keylen, digest };
}
/**
* @param iterations Needs to be higher or equal than zero
* @param keylen Needs to be higher or equal than zero but less than max allocation size (2^30)
* @param digest Algorithm to be used for encryption
*/
export function pbkdf2Sync(
password: HASH_DATA,
salt: HASH_DATA,
iterations: number,
keylen: number,
digest: string,
): Buffer {
({ password, salt, iterations, keylen, digest } = check(
password,
salt,
iterations,
keylen,
digest,
));
digest = digest.toLowerCase() as NormalizedAlgorithms;
const DK = new Uint8Array(keylen);
if (!op_node_pbkdf2(password, salt, iterations, digest, DK)) {
throw new ERR_CRYPTO_INVALID_DIGEST(digest);
}
return Buffer.from(DK);
}
/**
* @param iterations Needs to be higher or equal than zero
* @param keylen Needs to be higher or equal than zero but less than max allocation size (2^30)
* @param digest Algorithm to be used for encryption
*/
export function pbkdf2(
password: HASH_DATA,
salt: HASH_DATA,
iterations: number,
keylen: number,
digest: string,
callback: (err: Error | null, derivedKey?: Buffer) => void,
) {
if (typeof digest === "function") {
callback = digest;
digest = undefined as unknown as string;
}
({ password, salt, iterations, keylen, digest } = check(
password,
salt,
iterations,
keylen,
digest,
));
validateFunction(callback, "callback");
digest = digest.toLowerCase() as NormalizedAlgorithms;
op_node_pbkdf2_async(
password,
salt,
iterations,
digest,
keylen,
).then(
(DK) => callback(null, Buffer.from(DK)),
)
.catch((err) => callback(err));
}
export default {
MAX_ALLOC,
pbkdf2,
pbkdf2Sync,
};