@zudojs/crypto 1.1.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -1
- package/dist/cryptoCipher/cryptoCipher.core.js +3 -0
- package/dist/cryptoConstants/cryptoConstants.security.d.ts +16 -4
- package/dist/cryptoConstants/cryptoConstants.security.js +16 -4
- package/dist/cryptoHash/cryptoHash.core.js +2 -0
- package/dist/cryptoHash/cryptoHmac.core.js +2 -0
- package/dist/cryptoKey/cryptoKey.factory.js +4 -0
- package/dist/cryptoKeyDerivation/cryptoKeyDerivation.core.js +6 -0
- package/dist/cryptoKeyDerivation/cryptoKeyDerivation.salt.js +2 -0
- package/dist/cryptoPassword/cryptoPassword.core.js +7 -1
- package/dist/cryptoPassword/cryptoPassword.helper.d.ts +11 -0
- package/dist/cryptoPassword/cryptoPassword.helper.js +16 -1
- package/dist/cryptoProvider/cryptoProvider.capability.d.ts +48 -0
- package/dist/cryptoProvider/cryptoProvider.capability.js +68 -0
- package/dist/cryptoProvider/cryptoProvider.default.d.ts +21 -0
- package/dist/cryptoProvider/cryptoProvider.default.js +64 -0
- package/dist/cryptoProvider/index.d.ts +2 -1
- package/dist/cryptoProvider/index.js +2 -1
- package/dist/cryptoRandom/cryptoRandom.choice.js +4 -0
- package/dist/cryptoRandom/cryptoRandom.core.js +5 -0
- package/dist/cryptoRandom/cryptoRandom.string.js +5 -0
- package/dist/cryptoService/cryptoService.core.js +2 -0
- package/dist/cryptoService/operations/cryptoService.hash.js +2 -0
- package/dist/cryptoSignature/cryptoSignature.core.js +4 -0
- package/dist/cryptoToken/cryptoToken.core.js +2 -0
- package/dist/cryptoToken/cryptoToken.hash.js +3 -0
- package/dist/node/nodeCryptoProvider/operations/nodeCryptoProvider.password.d.ts +4 -3
- package/dist/node/nodeCryptoProvider/operations/nodeCryptoProvider.password.js +7 -4
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
Cryptographic primitives for hashing, authenticated encryption, password hashing, digital signatures, key derivation, opaque tokens, and secure random generation. Everything is backed by `node:crypto` through a swappable provider.
|
|
4
4
|
|
|
5
|
+
<!-- zudo-docs:start -->
|
|
6
|
+
|
|
7
|
+
**Documentation:** [zudojs.oyinlola.site/docs/packages-crypto](https://zudojs.oyinlola.site/docs/packages-crypto) · **For AI agents:** [Markdown version](https://zudojs.oyinlola.site/docs/packages-crypto.md), [llms.txt](https://zudojs.oyinlola.site/llms.txt)
|
|
8
|
+
|
|
9
|
+
<!-- zudo-docs:end -->
|
|
10
|
+
|
|
5
11
|
## Installation
|
|
6
12
|
|
|
7
13
|
```bash
|
|
@@ -42,7 +48,7 @@ const sessionToken = await generateToken({ bytes: 32, prefix: "sess_" });
|
|
|
42
48
|
|
|
43
49
|
- Hashing: SHA-256/384/512 and SHA3-256/384/512, HMAC (keys of at least 16 bytes)
|
|
44
50
|
- Authenticated encryption: AES-256-GCM with strict IV (12 bytes) and tag (16 bytes) validation, plus a versioned string envelope
|
|
45
|
-
- Password hashing: scrypt (default, OWASP
|
|
51
|
+
- Password hashing: scrypt (default N=2^14, r=8, p=5 — the OWASP row for that N) and PBKDF2-HMAC (provider level), versioned self-describing encoding, bounded parameters on verification. New hashes must use a cost of at least `PASSWORD_HASH.SCRYPT.MIN_COST` (16 384); older stored hashes with a smaller cost still verify. Key derivation (`deriveScrypt`) keeps p=1 by default, so derived keys do not change
|
|
46
52
|
- Key derivation: PBKDF2 (sha256/384/512, 600 000 iterations by default) and scrypt (cost, block size, parallelization, memory bound); every work factor and the output length are capped by `PASSWORD_HASH.LIMITS`, so a value read from configuration cannot request unbounded CPU or memory
|
|
47
53
|
- Digital signatures: Ed25519, RSA-SHA256/384/512, ECDSA-SHA256/384/512; the algorithm label is bound to the key type
|
|
48
54
|
- Secure random: unbiased integers up to 2^48, UUID v4, bytes, alphabets, numeric codes
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { getDefaultCryptoProvider } from "../cryptoProvider/cryptoProvider.default.js";
|
|
2
|
+
import { assertProviderCapability } from "../cryptoProvider/cryptoProvider.capability.js";
|
|
2
3
|
import { AES_GCM } from "../cryptoConstants/cryptoConstants.type.js";
|
|
3
4
|
import { CryptoOperation } from "@zudojs/errors";
|
|
4
5
|
import { cipherError } from "../cryptoErrors/cryptoErrors.helper.js";
|
|
@@ -10,6 +11,7 @@ export async function encrypt(plaintext, key, options = {}) {
|
|
|
10
11
|
throw cipherError(`AES-256-GCM iv must be ${AES_GCM.IV_BYTES} bytes.`, CryptoOperation.ENCRYPT, "aes-256-gcm");
|
|
11
12
|
}
|
|
12
13
|
const provider = options.provider ?? getDefaultCryptoProvider();
|
|
14
|
+
assertProviderCapability(provider, "encryption", CryptoOperation.ENCRYPT);
|
|
13
15
|
const encrypted = await provider.encrypt({
|
|
14
16
|
key,
|
|
15
17
|
plaintext,
|
|
@@ -37,6 +39,7 @@ export async function decrypt(ciphertext, key, iv, authTag, aad, provider = getD
|
|
|
37
39
|
authTag.byteLength !== AES_GCM.AUTH_TAG_BYTES) {
|
|
38
40
|
throw cipherError(`AES-256-GCM authentication tag must be ${AES_GCM.AUTH_TAG_BYTES} bytes.`, CryptoOperation.DECRYPT, "aes-256-gcm");
|
|
39
41
|
}
|
|
42
|
+
assertProviderCapability(provider, "encryption", CryptoOperation.DECRYPT);
|
|
40
43
|
return provider.decrypt({
|
|
41
44
|
key,
|
|
42
45
|
encrypted: {
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Password hashing constants.
|
|
3
3
|
*
|
|
4
|
-
* Defaults follow
|
|
5
|
-
*
|
|
6
|
-
* iterations. `LIMITS` bounds parameters
|
|
7
|
-
* hashes so that a tampered or foreign
|
|
4
|
+
* Defaults follow the OWASP Password Storage Cheat Sheet: scrypt N=2^14,
|
|
5
|
+
* r=8, p=5 (OWASP's N=2^14 row; p=1 is only adequate at N=2^17) and
|
|
6
|
+
* PBKDF2-HMAC-SHA256 with 600 000 iterations. `LIMITS` bounds parameters
|
|
7
|
+
* that are read back from stored hashes so that a tampered or foreign
|
|
8
|
+
* record cannot force unbounded work.
|
|
9
|
+
*
|
|
10
|
+
* New hashes are held to `SCRYPT.MIN_COST`; verification still accepts
|
|
11
|
+
* older stored hashes down to `LIMITS.MIN_SCRYPT_COST`.
|
|
8
12
|
*/
|
|
9
13
|
export declare const PASSWORD_HASH: Readonly<{
|
|
10
14
|
readonly SALT_BYTES: 16;
|
|
@@ -12,7 +16,15 @@ export declare const PASSWORD_HASH: Readonly<{
|
|
|
12
16
|
readonly SCRYPT: Readonly<{
|
|
13
17
|
COST: 16384;
|
|
14
18
|
BLOCK_SIZE: 8;
|
|
19
|
+
/**
|
|
20
|
+
* Default `p` for key derivation (`deriveScrypt`). Left at 1: changing
|
|
21
|
+
* it would silently change every key derived with default options.
|
|
22
|
+
*/
|
|
15
23
|
PARALLELIZATION: 1;
|
|
24
|
+
/** Default `p` for new *password hashes* (OWASP N=2^14, r=8, p=5). */
|
|
25
|
+
PASSWORD_PARALLELIZATION: 5;
|
|
26
|
+
/** Floor on the cost of *new* hashes; `LIMITS.MIN_SCRYPT_COST` bounds verification. */
|
|
27
|
+
MIN_COST: 16384;
|
|
16
28
|
}>;
|
|
17
29
|
readonly PBKDF2: Readonly<{
|
|
18
30
|
ITERATIONS: 600000;
|
|
@@ -2,10 +2,14 @@ import { TimeMs } from "@zudojs/constants";
|
|
|
2
2
|
/**
|
|
3
3
|
* Password hashing constants.
|
|
4
4
|
*
|
|
5
|
-
* Defaults follow
|
|
6
|
-
*
|
|
7
|
-
* iterations. `LIMITS` bounds parameters
|
|
8
|
-
* hashes so that a tampered or foreign
|
|
5
|
+
* Defaults follow the OWASP Password Storage Cheat Sheet: scrypt N=2^14,
|
|
6
|
+
* r=8, p=5 (OWASP's N=2^14 row; p=1 is only adequate at N=2^17) and
|
|
7
|
+
* PBKDF2-HMAC-SHA256 with 600 000 iterations. `LIMITS` bounds parameters
|
|
8
|
+
* that are read back from stored hashes so that a tampered or foreign
|
|
9
|
+
* record cannot force unbounded work.
|
|
10
|
+
*
|
|
11
|
+
* New hashes are held to `SCRYPT.MIN_COST`; verification still accepts
|
|
12
|
+
* older stored hashes down to `LIMITS.MIN_SCRYPT_COST`.
|
|
9
13
|
*/
|
|
10
14
|
export const PASSWORD_HASH = Object.freeze({
|
|
11
15
|
SALT_BYTES: 16,
|
|
@@ -13,7 +17,15 @@ export const PASSWORD_HASH = Object.freeze({
|
|
|
13
17
|
SCRYPT: Object.freeze({
|
|
14
18
|
COST: 16_384,
|
|
15
19
|
BLOCK_SIZE: 8,
|
|
20
|
+
/**
|
|
21
|
+
* Default `p` for key derivation (`deriveScrypt`). Left at 1: changing
|
|
22
|
+
* it would silently change every key derived with default options.
|
|
23
|
+
*/
|
|
16
24
|
PARALLELIZATION: 1,
|
|
25
|
+
/** Default `p` for new *password hashes* (OWASP N=2^14, r=8, p=5). */
|
|
26
|
+
PASSWORD_PARALLELIZATION: 5,
|
|
27
|
+
/** Floor on the cost of *new* hashes; `LIMITS.MIN_SCRYPT_COST` bounds verification. */
|
|
28
|
+
MIN_COST: 16_384,
|
|
17
29
|
}),
|
|
18
30
|
PBKDF2: Object.freeze({
|
|
19
31
|
ITERATIONS: 600_000,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { getDefaultCryptoProvider } from "../cryptoProvider/cryptoProvider.default.js";
|
|
2
|
+
import { assertHashCapability } from "../cryptoProvider/cryptoProvider.capability.js";
|
|
2
3
|
import { isHashAlgorithmName } from "../cryptoProvider/cryptoProvider.type.js";
|
|
3
4
|
import { encodeDigest } from "./cryptoHash.codec.js";
|
|
4
5
|
/**
|
|
@@ -13,6 +14,7 @@ export async function hash(input, options = {}) {
|
|
|
13
14
|
throw new TypeError(`Unsupported hash algorithm: ${String(algorithm)}.`);
|
|
14
15
|
}
|
|
15
16
|
const provider = options.provider ?? getDefaultCryptoProvider();
|
|
17
|
+
assertHashCapability(provider);
|
|
16
18
|
const digest = await provider.hash(algorithm, input);
|
|
17
19
|
const encoding = options.encoding ?? "hex";
|
|
18
20
|
return Object.freeze({
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { getDefaultCryptoProvider } from "../cryptoProvider/cryptoProvider.default.js";
|
|
2
|
+
import { assertHmacCapability } from "../cryptoProvider/cryptoProvider.capability.js";
|
|
2
3
|
import { isHmacAlgorithmName } from "../cryptoProvider/cryptoProvider.type.js";
|
|
3
4
|
import { KEY_SIZE } from "../cryptoConstants/cryptoConstants.type.js";
|
|
4
5
|
import { encodeDigest } from "./cryptoHash.codec.js";
|
|
@@ -18,6 +19,7 @@ export async function hmac(input, key, algorithm = "sha256", encoding = "hex", p
|
|
|
18
19
|
if (key.byteLength < KEY_SIZE.MIN_HMAC_KEY_BYTES) {
|
|
19
20
|
throw new RangeError(`HMAC key must be at least ${KEY_SIZE.MIN_HMAC_KEY_BYTES} bytes.`);
|
|
20
21
|
}
|
|
22
|
+
assertHmacCapability(provider);
|
|
21
23
|
const digest = await provider.hmac(algorithm, key, input);
|
|
22
24
|
return encodeDigest(digest, encoding);
|
|
23
25
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { getDefaultCryptoProvider } from "../cryptoProvider/cryptoProvider.default.js";
|
|
2
|
+
import { assertHmacCapability, assertRandomCapability, } from "../cryptoProvider/cryptoProvider.capability.js";
|
|
2
3
|
import { encode } from "../cryptoEncoding/cryptoEncoding.core.js";
|
|
3
4
|
import { CryptoOperation } from "@zudojs/errors";
|
|
4
5
|
import { keyError } from "../cryptoErrors/cryptoErrors.helper.js";
|
|
@@ -58,6 +59,8 @@ function validateKeyBytes(bytes, algorithm) {
|
|
|
58
59
|
* Hashing and key-id generation are delegated to the supplied provider.
|
|
59
60
|
*/
|
|
60
61
|
export async function createCryptoKey(bytes, options, provider = getDefaultCryptoProvider()) {
|
|
62
|
+
assertHmacCapability(provider);
|
|
63
|
+
assertRandomCapability(provider);
|
|
61
64
|
validateKeyBytes(bytes, options.algorithm);
|
|
62
65
|
const keyBytes = new Uint8Array(bytes);
|
|
63
66
|
const digest = await provider.hmac("sha256", CRYPTO_KEY_FINGERPRINT_LABEL, keyBytes);
|
|
@@ -90,6 +93,7 @@ export async function generateCryptoKey(length, options, provider = getDefaultCr
|
|
|
90
93
|
if (!Number.isInteger(length) || length <= 0) {
|
|
91
94
|
throw new RangeError("Cryptographic key length must be a positive integer.");
|
|
92
95
|
}
|
|
96
|
+
assertRandomCapability(provider);
|
|
93
97
|
const bytes = await provider.randomBytes(length);
|
|
94
98
|
return createCryptoKey(bytes, options, provider);
|
|
95
99
|
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { getDefaultCryptoProvider } from "../cryptoProvider/cryptoProvider.default.js";
|
|
2
|
+
import { CryptoOperation } from "@zudojs/errors";
|
|
3
|
+
import { assertProviderCapability, assertRandomCapability, } from "../cryptoProvider/cryptoProvider.capability.js";
|
|
2
4
|
import { CryptoAlgorithm } from "../cryptoConstants/cryptoConstants.type.js";
|
|
3
5
|
import { PASSWORD_HASH } from "../cryptoConstants/cryptoConstants.security.js";
|
|
4
6
|
import { validatePbkdf2Options, validateScryptOptions, } from "./cryptoKeyDerivation.validate.js";
|
|
@@ -20,6 +22,8 @@ function pbkdf2Label(digest) {
|
|
|
20
22
|
*/
|
|
21
23
|
export async function derivePbkdf2(password, options = {}) {
|
|
22
24
|
const provider = options.provider ?? getDefaultCryptoProvider();
|
|
25
|
+
assertProviderCapability(provider, "keyDerivation", CryptoOperation.KEY_DERIVATION);
|
|
26
|
+
assertRandomCapability(provider);
|
|
23
27
|
const iterations = options.iterations ?? PASSWORD_HASH.PBKDF2.ITERATIONS;
|
|
24
28
|
const keyLength = options.keyLength ?? 32;
|
|
25
29
|
const digest = options.digest ?? "sha256";
|
|
@@ -49,6 +53,8 @@ export async function derivePbkdf2(password, options = {}) {
|
|
|
49
53
|
*/
|
|
50
54
|
export async function deriveScrypt(password, options = {}) {
|
|
51
55
|
const provider = options.provider ?? getDefaultCryptoProvider();
|
|
56
|
+
assertProviderCapability(provider, "keyDerivation", CryptoOperation.KEY_DERIVATION);
|
|
57
|
+
assertRandomCapability(provider);
|
|
52
58
|
const keyLength = options.keyLength ?? 32;
|
|
53
59
|
const cost = options.cost ?? PASSWORD_HASH.SCRYPT.COST;
|
|
54
60
|
const blockSize = options.blockSize ?? PASSWORD_HASH.SCRYPT.BLOCK_SIZE;
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { getDefaultCryptoProvider } from "../cryptoProvider/cryptoProvider.default.js";
|
|
2
|
+
import { assertRandomCapability } from "../cryptoProvider/cryptoProvider.capability.js";
|
|
2
3
|
/**
|
|
3
4
|
* Creates a random salt.
|
|
4
5
|
*/
|
|
5
6
|
export async function generateSalt(length = 16, provider = getDefaultCryptoProvider()) {
|
|
7
|
+
assertRandomCapability(provider);
|
|
6
8
|
if (!Number.isInteger(length) || length < 16) {
|
|
7
9
|
throw new RangeError("Salt length must be an integer of at least 16 bytes.");
|
|
8
10
|
}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { getDefaultCryptoProvider } from "../cryptoProvider/cryptoProvider.default.js";
|
|
2
|
+
import { assertPasswordHashingCapability, assertRandomCapability, } from "../cryptoProvider/cryptoProvider.capability.js";
|
|
2
3
|
import { CryptoAlgorithm } from "../cryptoConstants/cryptoConstants.type.js";
|
|
3
4
|
import { PASSWORD_HASH } from "../cryptoConstants/cryptoConstants.security.js";
|
|
4
5
|
import { assertPassword, validateParameters, } from "./cryptoPassword.validate.js";
|
|
5
6
|
import { decodePasswordHash, PASSWORD_FORMAT_VERSION, } from "./cryptoPassword.codec.js";
|
|
7
|
+
import { assertNewHashCost } from "./cryptoPassword.helper.js";
|
|
6
8
|
/**
|
|
7
9
|
* Hashes a password using scrypt.
|
|
8
10
|
*
|
|
@@ -12,11 +14,14 @@ import { decodePasswordHash, PASSWORD_FORMAT_VERSION, } from "./cryptoPassword.c
|
|
|
12
14
|
export async function hashPassword(password, options = {}) {
|
|
13
15
|
assertPassword(password);
|
|
14
16
|
const provider = options.provider ?? getDefaultCryptoProvider();
|
|
17
|
+
assertPasswordHashingCapability(provider);
|
|
18
|
+
assertRandomCapability(provider);
|
|
15
19
|
const saltBytes = options.saltBytes ?? PASSWORD_HASH.SALT_BYTES;
|
|
16
20
|
const keyBytes = options.keyBytes ?? PASSWORD_HASH.KEY_BYTES;
|
|
17
21
|
const cost = options.cost ?? PASSWORD_HASH.SCRYPT.COST;
|
|
18
22
|
const blockSize = options.blockSize ?? PASSWORD_HASH.SCRYPT.BLOCK_SIZE;
|
|
19
|
-
const parallelization = options.parallelization ?? PASSWORD_HASH.SCRYPT.
|
|
23
|
+
const parallelization = options.parallelization ?? PASSWORD_HASH.SCRYPT.PASSWORD_PARALLELIZATION;
|
|
24
|
+
assertNewHashCost(cost);
|
|
20
25
|
validateParameters({
|
|
21
26
|
saltBytes,
|
|
22
27
|
keyBytes,
|
|
@@ -56,6 +61,7 @@ export async function hashPassword(password, options = {}) {
|
|
|
56
61
|
* password also yields false.
|
|
57
62
|
*/
|
|
58
63
|
export async function verifyPassword(password, encoded, provider = getDefaultCryptoProvider()) {
|
|
64
|
+
assertPasswordHashingCapability(provider);
|
|
59
65
|
try {
|
|
60
66
|
assertPassword(password);
|
|
61
67
|
return await provider.verifyPassword(password, encoded);
|
|
@@ -11,4 +11,15 @@ export declare function isPasswordHash(encoded: string): boolean;
|
|
|
11
11
|
* Returns whether a password meets the basic requirements.
|
|
12
12
|
*/
|
|
13
13
|
export declare function isValidPassword(password: string, minimumLength?: number): boolean;
|
|
14
|
+
/**
|
|
15
|
+
* Refuses an scrypt cost below `PASSWORD_HASH.SCRYPT.MIN_COST` for a *new*
|
|
16
|
+
* password hash.
|
|
17
|
+
*
|
|
18
|
+
* `LIMITS.MIN_SCRYPT_COST` (2) only bounds what verification accepts, so
|
|
19
|
+
* older stored hashes stay verifiable; minting a new hash with `cost: 2`
|
|
20
|
+
* produced a hash that is effectively free to crack.
|
|
21
|
+
*
|
|
22
|
+
* @throws {RangeError} when `cost` is below the floor.
|
|
23
|
+
*/
|
|
24
|
+
export declare function assertNewHashCost(cost: number): void;
|
|
14
25
|
//# sourceMappingURL=cryptoPassword.helper.d.ts.map
|
|
@@ -10,7 +10,7 @@ export function getDefaultPasswordHashOptions() {
|
|
|
10
10
|
keyBytes: PASSWORD_HASH.KEY_BYTES,
|
|
11
11
|
cost: PASSWORD_HASH.SCRYPT.COST,
|
|
12
12
|
blockSize: PASSWORD_HASH.SCRYPT.BLOCK_SIZE,
|
|
13
|
-
parallelization: PASSWORD_HASH.SCRYPT.
|
|
13
|
+
parallelization: PASSWORD_HASH.SCRYPT.PASSWORD_PARALLELIZATION,
|
|
14
14
|
};
|
|
15
15
|
}
|
|
16
16
|
/**
|
|
@@ -31,4 +31,19 @@ export function isPasswordHash(encoded) {
|
|
|
31
31
|
export function isValidPassword(password, minimumLength = PASSWORD_MINIMUM_DEFAULT_LENGTH) {
|
|
32
32
|
return typeof password === "string" && password.length >= minimumLength;
|
|
33
33
|
}
|
|
34
|
+
/**
|
|
35
|
+
* Refuses an scrypt cost below `PASSWORD_HASH.SCRYPT.MIN_COST` for a *new*
|
|
36
|
+
* password hash.
|
|
37
|
+
*
|
|
38
|
+
* `LIMITS.MIN_SCRYPT_COST` (2) only bounds what verification accepts, so
|
|
39
|
+
* older stored hashes stay verifiable; minting a new hash with `cost: 2`
|
|
40
|
+
* produced a hash that is effectively free to crack.
|
|
41
|
+
*
|
|
42
|
+
* @throws {RangeError} when `cost` is below the floor.
|
|
43
|
+
*/
|
|
44
|
+
export function assertNewHashCost(cost) {
|
|
45
|
+
if (typeof cost === "number" && cost < PASSWORD_HASH.SCRYPT.MIN_COST) {
|
|
46
|
+
throw new RangeError(`scrypt cost for a new password hash must be at least ${PASSWORD_HASH.SCRYPT.MIN_COST}.`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
34
49
|
//# sourceMappingURL=cryptoPassword.helper.js.map
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Capability checks for a crypto provider.
|
|
3
|
+
*
|
|
4
|
+
* `CryptoCapabilities` is required on every provider, but nothing read it:
|
|
5
|
+
* a provider declaring `signing: false` still had `sign` called, and a
|
|
6
|
+
* provider that only implements part of the interface only failed once the
|
|
7
|
+
* missing method was reached — as a bare `TypeError` from deep inside an
|
|
8
|
+
* unrelated operation. Both are checked here instead, so an unsupported
|
|
9
|
+
* operation is refused by name at the boundary.
|
|
10
|
+
*
|
|
11
|
+
* @module cryptoProvider/cryptoProvider.capability
|
|
12
|
+
*/
|
|
13
|
+
import { CryptoOperation } from "@zudojs/errors";
|
|
14
|
+
import type { CryptoProvider } from "./cryptoProvider.core.js";
|
|
15
|
+
import type { CryptoCapabilities } from "./cryptoProvider.type.js";
|
|
16
|
+
/**
|
|
17
|
+
* Throws unless the provider declares the capability an operation needs.
|
|
18
|
+
*
|
|
19
|
+
* A missing or malformed `capabilities` object fails the same way a `false`
|
|
20
|
+
* flag does: a provider that cannot describe itself is not one a
|
|
21
|
+
* secret-handling path should call blind.
|
|
22
|
+
*
|
|
23
|
+
* @param provider - The provider about to be used.
|
|
24
|
+
* @param capability - The capability the operation requires.
|
|
25
|
+
* @param operation - The operation being attempted, for the error.
|
|
26
|
+
* @throws {CryptoError} when the capability is not declared.
|
|
27
|
+
*/
|
|
28
|
+
export declare function assertProviderCapability(provider: CryptoProvider, capability: keyof CryptoCapabilities, operation: CryptoOperation): void;
|
|
29
|
+
/**
|
|
30
|
+
* Throws unless the provider declares `random`. @see assertProviderCapability
|
|
31
|
+
*
|
|
32
|
+
* Every helper that draws bytes — tokens, salts, ids, nonces — calls this, so
|
|
33
|
+
* the operation name is fixed in one place rather than at each call site.
|
|
34
|
+
*/
|
|
35
|
+
export declare function assertRandomCapability(provider: CryptoProvider): void;
|
|
36
|
+
/** Throws unless the provider declares `hash`. @see assertProviderCapability */
|
|
37
|
+
export declare function assertHashCapability(provider: CryptoProvider): void;
|
|
38
|
+
/** Throws unless the provider declares `hmac`. @see assertProviderCapability */
|
|
39
|
+
export declare function assertHmacCapability(provider: CryptoProvider): void;
|
|
40
|
+
/**
|
|
41
|
+
* Throws unless the provider declares `passwordHashing`.
|
|
42
|
+
*
|
|
43
|
+
* `verifyPassword` answers `false` for anything wrong with the password, so a
|
|
44
|
+
* provider that cannot hash would read as "wrong password" forever. That is a
|
|
45
|
+
* configuration failure, and it throws.
|
|
46
|
+
*/
|
|
47
|
+
export declare function assertPasswordHashingCapability(provider: CryptoProvider): void;
|
|
48
|
+
//# sourceMappingURL=cryptoProvider.capability.d.ts.map
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Capability checks for a crypto provider.
|
|
3
|
+
*
|
|
4
|
+
* `CryptoCapabilities` is required on every provider, but nothing read it:
|
|
5
|
+
* a provider declaring `signing: false` still had `sign` called, and a
|
|
6
|
+
* provider that only implements part of the interface only failed once the
|
|
7
|
+
* missing method was reached — as a bare `TypeError` from deep inside an
|
|
8
|
+
* unrelated operation. Both are checked here instead, so an unsupported
|
|
9
|
+
* operation is refused by name at the boundary.
|
|
10
|
+
*
|
|
11
|
+
* @module cryptoProvider/cryptoProvider.capability
|
|
12
|
+
*/
|
|
13
|
+
import { CryptoError, CryptoOperation, ErrorCode } from "@zudojs/errors";
|
|
14
|
+
/** The provider's reported name, without trusting it to be a string. */
|
|
15
|
+
function providerName(provider) {
|
|
16
|
+
return typeof provider.name === "string" && provider.name.length > 0
|
|
17
|
+
? provider.name
|
|
18
|
+
: "unnamed";
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Throws unless the provider declares the capability an operation needs.
|
|
22
|
+
*
|
|
23
|
+
* A missing or malformed `capabilities` object fails the same way a `false`
|
|
24
|
+
* flag does: a provider that cannot describe itself is not one a
|
|
25
|
+
* secret-handling path should call blind.
|
|
26
|
+
*
|
|
27
|
+
* @param provider - The provider about to be used.
|
|
28
|
+
* @param capability - The capability the operation requires.
|
|
29
|
+
* @param operation - The operation being attempted, for the error.
|
|
30
|
+
* @throws {CryptoError} when the capability is not declared.
|
|
31
|
+
*/
|
|
32
|
+
export function assertProviderCapability(provider, capability, operation) {
|
|
33
|
+
const capabilities = provider.capabilities;
|
|
34
|
+
if (typeof capabilities !== "object" ||
|
|
35
|
+
capabilities === null ||
|
|
36
|
+
capabilities[capability] !== true) {
|
|
37
|
+
throw new CryptoError(`Crypto provider "${providerName(provider)}" does not support ` +
|
|
38
|
+
`${capability}: the "${operation}" operation was refused.`, { code: ErrorCode.CRYPTO, operation });
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Throws unless the provider declares `random`. @see assertProviderCapability
|
|
43
|
+
*
|
|
44
|
+
* Every helper that draws bytes — tokens, salts, ids, nonces — calls this, so
|
|
45
|
+
* the operation name is fixed in one place rather than at each call site.
|
|
46
|
+
*/
|
|
47
|
+
export function assertRandomCapability(provider) {
|
|
48
|
+
assertProviderCapability(provider, "random", CryptoOperation.RANDOM);
|
|
49
|
+
}
|
|
50
|
+
/** Throws unless the provider declares `hash`. @see assertProviderCapability */
|
|
51
|
+
export function assertHashCapability(provider) {
|
|
52
|
+
assertProviderCapability(provider, "hash", CryptoOperation.HASH);
|
|
53
|
+
}
|
|
54
|
+
/** Throws unless the provider declares `hmac`. @see assertProviderCapability */
|
|
55
|
+
export function assertHmacCapability(provider) {
|
|
56
|
+
assertProviderCapability(provider, "hmac", CryptoOperation.HASH);
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Throws unless the provider declares `passwordHashing`.
|
|
60
|
+
*
|
|
61
|
+
* `verifyPassword` answers `false` for anything wrong with the password, so a
|
|
62
|
+
* provider that cannot hash would read as "wrong password" forever. That is a
|
|
63
|
+
* configuration failure, and it throws.
|
|
64
|
+
*/
|
|
65
|
+
export function assertPasswordHashingCapability(provider) {
|
|
66
|
+
assertProviderCapability(provider, "passwordHashing", CryptoOperation.KEY_DERIVATION);
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=cryptoProvider.capability.js.map
|
|
@@ -1,4 +1,18 @@
|
|
|
1
1
|
import type { CryptoProvider } from "./cryptoProvider.core.js";
|
|
2
|
+
/** Every method a `CryptoProvider` must implement. */
|
|
3
|
+
export declare const CRYPTO_PROVIDER_METHODS: readonly ["randomBytes", "randomInt", "randomUUID", "hash", "hmac", "encrypt", "decrypt", "sign", "verify", "deriveKey", "hashPassword", "verifyPassword"];
|
|
4
|
+
/**
|
|
5
|
+
* Throws unless `provider` is a complete {@link CryptoProvider}.
|
|
6
|
+
*
|
|
7
|
+
* Installing `{}` as the process-wide default used to succeed and then fail
|
|
8
|
+
* at the first `randomHex(16)` with a `TypeError` — arbitrarily far from the
|
|
9
|
+
* call that caused it. Every method and every capability flag is checked
|
|
10
|
+
* here, at install time.
|
|
11
|
+
*
|
|
12
|
+
* @param provider - The candidate provider.
|
|
13
|
+
* @throws {CryptoError} when a method or a capability flag is missing.
|
|
14
|
+
*/
|
|
15
|
+
export declare function assertCryptoProvider(provider: CryptoProvider): asserts provider is CryptoProvider;
|
|
2
16
|
/**
|
|
3
17
|
* Returns the process-wide default crypto provider, creating the Node
|
|
4
18
|
* provider lazily on first use.
|
|
@@ -9,6 +23,13 @@ export declare function getDefaultCryptoProvider(): CryptoProvider;
|
|
|
9
23
|
*
|
|
10
24
|
* Every module-level helper (hash, encrypt, hashPassword, generateToken,
|
|
11
25
|
* ...) that is not given an explicit provider uses this one.
|
|
26
|
+
*
|
|
27
|
+
* The provider is checked here rather than at first use: installing a partial
|
|
28
|
+
* object succeeded and then failed much later, inside whichever operation
|
|
29
|
+
* happened to reach the missing method first.
|
|
30
|
+
*
|
|
31
|
+
* @param provider - The provider to install process-wide.
|
|
32
|
+
* @throws {CryptoError} when a method or a capability flag is missing.
|
|
12
33
|
*/
|
|
13
34
|
export declare function setDefaultCryptoProvider(provider: CryptoProvider): void;
|
|
14
35
|
/**
|
|
@@ -1,4 +1,60 @@
|
|
|
1
|
+
import { CryptoError, CryptoOperation, ErrorCode } from "@zudojs/errors";
|
|
1
2
|
import { createNodeCryptoProvider } from "../node/nodeCryptoProvider/nodeCryptoProvider.factory.js";
|
|
3
|
+
/** Every method a `CryptoProvider` must implement. */
|
|
4
|
+
export const CRYPTO_PROVIDER_METHODS = Object.freeze([
|
|
5
|
+
"randomBytes",
|
|
6
|
+
"randomInt",
|
|
7
|
+
"randomUUID",
|
|
8
|
+
"hash",
|
|
9
|
+
"hmac",
|
|
10
|
+
"encrypt",
|
|
11
|
+
"decrypt",
|
|
12
|
+
"sign",
|
|
13
|
+
"verify",
|
|
14
|
+
"deriveKey",
|
|
15
|
+
"hashPassword",
|
|
16
|
+
"verifyPassword",
|
|
17
|
+
]);
|
|
18
|
+
/** Every capability flag a `CryptoProvider` must declare. */
|
|
19
|
+
const CRYPTO_CAPABILITY_FLAGS = Object.freeze([
|
|
20
|
+
"hash",
|
|
21
|
+
"hmac",
|
|
22
|
+
"encryption",
|
|
23
|
+
"signing",
|
|
24
|
+
"random",
|
|
25
|
+
"passwordHashing",
|
|
26
|
+
"keyDerivation",
|
|
27
|
+
]);
|
|
28
|
+
/**
|
|
29
|
+
* Throws unless `provider` is a complete {@link CryptoProvider}.
|
|
30
|
+
*
|
|
31
|
+
* Installing `{}` as the process-wide default used to succeed and then fail
|
|
32
|
+
* at the first `randomHex(16)` with a `TypeError` — arbitrarily far from the
|
|
33
|
+
* call that caused it. Every method and every capability flag is checked
|
|
34
|
+
* here, at install time.
|
|
35
|
+
*
|
|
36
|
+
* @param provider - The candidate provider.
|
|
37
|
+
* @throws {CryptoError} when a method or a capability flag is missing.
|
|
38
|
+
*/
|
|
39
|
+
export function assertCryptoProvider(provider) {
|
|
40
|
+
const candidate = provider;
|
|
41
|
+
for (const method of CRYPTO_PROVIDER_METHODS) {
|
|
42
|
+
if (typeof candidate[method] !== "function") {
|
|
43
|
+
throw new CryptoError(`Crypto provider is missing the "${method}" method: a provider must ` +
|
|
44
|
+
`implement all ${CRYPTO_PROVIDER_METHODS.length} operations.`, { code: ErrorCode.CRYPTO, operation: CryptoOperation.UNKNOWN });
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
const capabilities = candidate.capabilities;
|
|
48
|
+
if (typeof capabilities !== "object" || capabilities === null) {
|
|
49
|
+
throw new CryptoError("Crypto provider is missing its `capabilities` declaration.", { code: ErrorCode.CRYPTO, operation: CryptoOperation.UNKNOWN });
|
|
50
|
+
}
|
|
51
|
+
const flags = capabilities;
|
|
52
|
+
for (const flag of CRYPTO_CAPABILITY_FLAGS) {
|
|
53
|
+
if (typeof flags[flag] !== "boolean") {
|
|
54
|
+
throw new CryptoError(`Crypto provider capability "${flag}" must be a boolean.`, { code: ErrorCode.CRYPTO, operation: CryptoOperation.UNKNOWN });
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
2
58
|
let defaultProvider;
|
|
3
59
|
/**
|
|
4
60
|
* Returns the process-wide default crypto provider, creating the Node
|
|
@@ -15,11 +71,19 @@ export function getDefaultCryptoProvider() {
|
|
|
15
71
|
*
|
|
16
72
|
* Every module-level helper (hash, encrypt, hashPassword, generateToken,
|
|
17
73
|
* ...) that is not given an explicit provider uses this one.
|
|
74
|
+
*
|
|
75
|
+
* The provider is checked here rather than at first use: installing a partial
|
|
76
|
+
* object succeeded and then failed much later, inside whichever operation
|
|
77
|
+
* happened to reach the missing method first.
|
|
78
|
+
*
|
|
79
|
+
* @param provider - The provider to install process-wide.
|
|
80
|
+
* @throws {CryptoError} when a method or a capability flag is missing.
|
|
18
81
|
*/
|
|
19
82
|
export function setDefaultCryptoProvider(provider) {
|
|
20
83
|
if (typeof provider !== "object" || provider === null) {
|
|
21
84
|
throw new TypeError("Crypto provider must be an object.");
|
|
22
85
|
}
|
|
86
|
+
assertCryptoProvider(provider);
|
|
23
87
|
defaultProvider = provider;
|
|
24
88
|
}
|
|
25
89
|
/**
|
|
@@ -10,6 +10,7 @@ export type { CryptoProvider } from "./cryptoProvider.core.js";
|
|
|
10
10
|
export type { CryptoCapabilities, HashAlgorithm, HmacAlgorithm, Pbkdf2Digest, EncryptionAlgorithm, SignatureAlgorithm, KeyDerivationAlgorithm, EncodingFormat, CryptoInput, } from "./cryptoProvider.type.js";
|
|
11
11
|
export { isHashAlgorithmName, isHmacAlgorithmName, isPbkdf2Digest, isSignatureAlgorithmName, } from "./cryptoProvider.type.js";
|
|
12
12
|
export type { RandomProvider, HashProvider, HmacProvider, EncryptionProvider, SigningProvider, KeyDerivationProvider, PasswordProvider, } from "./cryptoProvider.interface.js";
|
|
13
|
-
export { getDefaultCryptoProvider, setDefaultCryptoProvider, resetDefaultCryptoProvider, } from "./cryptoProvider.default.js";
|
|
13
|
+
export { getDefaultCryptoProvider, setDefaultCryptoProvider, resetDefaultCryptoProvider, assertCryptoProvider, CRYPTO_PROVIDER_METHODS, } from "./cryptoProvider.default.js";
|
|
14
|
+
export { assertProviderCapability, assertRandomCapability, assertHashCapability, assertHmacCapability, assertPasswordHashingCapability, } from "./cryptoProvider.capability.js";
|
|
14
15
|
export * from "./types/index.js";
|
|
15
16
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { isHashAlgorithmName, isHmacAlgorithmName, isPbkdf2Digest, isSignatureAlgorithmName, } from "./cryptoProvider.type.js";
|
|
2
|
-
export { getDefaultCryptoProvider, setDefaultCryptoProvider, resetDefaultCryptoProvider, } from "./cryptoProvider.default.js";
|
|
2
|
+
export { getDefaultCryptoProvider, setDefaultCryptoProvider, resetDefaultCryptoProvider, assertCryptoProvider, CRYPTO_PROVIDER_METHODS, } from "./cryptoProvider.default.js";
|
|
3
|
+
export { assertProviderCapability, assertRandomCapability, assertHashCapability, assertHmacCapability, assertPasswordHashingCapability, } from "./cryptoProvider.capability.js";
|
|
3
4
|
export * from "./types/index.js";
|
|
4
5
|
//# sourceMappingURL=index.js.map
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { getDefaultCryptoProvider } from "../cryptoProvider/cryptoProvider.default.js";
|
|
2
|
+
import { assertRandomCapability } from "../cryptoProvider/cryptoProvider.capability.js";
|
|
2
3
|
/**
|
|
3
4
|
* Generates a cryptographically secure random boolean.
|
|
4
5
|
*/
|
|
5
6
|
export async function randomBoolean(provider = getDefaultCryptoProvider()) {
|
|
7
|
+
assertRandomCapability(provider);
|
|
6
8
|
return (await provider.randomInt(0, 2)) === 1;
|
|
7
9
|
}
|
|
8
10
|
/**
|
|
@@ -11,6 +13,7 @@ export async function randomBoolean(provider = getDefaultCryptoProvider()) {
|
|
|
11
13
|
* A single-element collection returns that element.
|
|
12
14
|
*/
|
|
13
15
|
export async function randomChoice(values, provider = getDefaultCryptoProvider()) {
|
|
16
|
+
assertRandomCapability(provider);
|
|
14
17
|
if (!Array.isArray(values) || values.length === 0) {
|
|
15
18
|
throw new RangeError("Cannot choose from an empty collection.");
|
|
16
19
|
}
|
|
@@ -21,6 +24,7 @@ export async function randomChoice(values, provider = getDefaultCryptoProvider()
|
|
|
21
24
|
* random bytes.
|
|
22
25
|
*/
|
|
23
26
|
export async function fillRandomBytes(target, provider = getDefaultCryptoProvider()) {
|
|
27
|
+
assertRandomCapability(provider);
|
|
24
28
|
if (!(target instanceof Uint8Array)) {
|
|
25
29
|
throw new TypeError("target must be a Uint8Array.");
|
|
26
30
|
}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { getDefaultCryptoProvider } from "../cryptoProvider/cryptoProvider.default.js";
|
|
2
|
+
import { assertRandomCapability } from "../cryptoProvider/cryptoProvider.capability.js";
|
|
2
3
|
/**
|
|
3
4
|
* Generates cryptographically secure random bytes.
|
|
4
5
|
*/
|
|
5
6
|
export async function randomBytesSecure(length, provider = getDefaultCryptoProvider()) {
|
|
7
|
+
assertRandomCapability(provider);
|
|
6
8
|
return provider.randomBytes(length);
|
|
7
9
|
}
|
|
8
10
|
/**
|
|
@@ -14,6 +16,7 @@ export async function randomBytesSecure(length, provider = getDefaultCryptoProvi
|
|
|
14
16
|
* Ranges up to 2^48 are supported; a range of one value returns `min`.
|
|
15
17
|
*/
|
|
16
18
|
export async function randomInteger(min, max, provider = getDefaultCryptoProvider()) {
|
|
19
|
+
assertRandomCapability(provider);
|
|
17
20
|
return provider.randomInt(min, max);
|
|
18
21
|
}
|
|
19
22
|
/**
|
|
@@ -21,12 +24,14 @@ export async function randomInteger(min, max, provider = getDefaultCryptoProvide
|
|
|
21
24
|
* from zero up to, but excluding, max.
|
|
22
25
|
*/
|
|
23
26
|
export async function randomIntegerBelow(max, provider = getDefaultCryptoProvider()) {
|
|
27
|
+
assertRandomCapability(provider);
|
|
24
28
|
return provider.randomInt(0, max);
|
|
25
29
|
}
|
|
26
30
|
/**
|
|
27
31
|
* Generates a random UUID v4.
|
|
28
32
|
*/
|
|
29
33
|
export async function randomUuid(provider = getDefaultCryptoProvider()) {
|
|
34
|
+
assertRandomCapability(provider);
|
|
30
35
|
return provider.randomUUID();
|
|
31
36
|
}
|
|
32
37
|
//# sourceMappingURL=cryptoRandom.core.js.map
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { getDefaultCryptoProvider } from "../cryptoProvider/cryptoProvider.default.js";
|
|
2
|
+
import { assertRandomCapability } from "../cryptoProvider/cryptoProvider.capability.js";
|
|
2
3
|
function assertLength(length, name = "length") {
|
|
3
4
|
if (!Number.isInteger(length) || length <= 0) {
|
|
4
5
|
throw new RangeError(`${name} must be a positive integer.`);
|
|
@@ -9,6 +10,7 @@ function assertLength(length, name = "length") {
|
|
|
9
10
|
*/
|
|
10
11
|
export async function randomHex(length, provider = getDefaultCryptoProvider()) {
|
|
11
12
|
assertLength(length);
|
|
13
|
+
assertRandomCapability(provider);
|
|
12
14
|
const bytes = await provider.randomBytes(Math.ceil(length / 2));
|
|
13
15
|
return Buffer.from(bytes).toString("hex").slice(0, length);
|
|
14
16
|
}
|
|
@@ -17,6 +19,7 @@ export async function randomHex(length, provider = getDefaultCryptoProvider()) {
|
|
|
17
19
|
*/
|
|
18
20
|
export async function randomBase64(byteLength, provider = getDefaultCryptoProvider()) {
|
|
19
21
|
assertLength(byteLength, "byteLength");
|
|
22
|
+
assertRandomCapability(provider);
|
|
20
23
|
const bytes = await provider.randomBytes(byteLength);
|
|
21
24
|
return Buffer.from(bytes).toString("base64");
|
|
22
25
|
}
|
|
@@ -25,6 +28,7 @@ export async function randomBase64(byteLength, provider = getDefaultCryptoProvid
|
|
|
25
28
|
*/
|
|
26
29
|
export async function randomBase64Url(byteLength, provider = getDefaultCryptoProvider()) {
|
|
27
30
|
assertLength(byteLength, "byteLength");
|
|
31
|
+
assertRandomCapability(provider);
|
|
28
32
|
const bytes = await provider.randomBytes(byteLength);
|
|
29
33
|
return Buffer.from(bytes).toString("base64url");
|
|
30
34
|
}
|
|
@@ -59,6 +63,7 @@ export async function randomAlphanumeric(length, provider = getDefaultCryptoProv
|
|
|
59
63
|
*/
|
|
60
64
|
export async function randomFromAlphabet(length, alphabet, provider = getDefaultCryptoProvider()) {
|
|
61
65
|
assertLength(length);
|
|
66
|
+
assertRandomCapability(provider);
|
|
62
67
|
if (typeof alphabet !== "string" || alphabet.length === 0) {
|
|
63
68
|
throw new RangeError("alphabet must not be empty.");
|
|
64
69
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { getDefaultCryptoProvider } from "../cryptoProvider/cryptoProvider.default.js";
|
|
2
|
+
import { assertRandomCapability } from "../cryptoProvider/cryptoProvider.capability.js";
|
|
2
3
|
import { generateCryptoKey, defaultKeyLength, } from "../cryptoKey/cryptoKey.factory.js";
|
|
3
4
|
import { CryptoAlgorithm } from "../cryptoConstants/cryptoConstants.type.js";
|
|
4
5
|
import { CryptoOperation } from "@zudojs/errors";
|
|
@@ -52,6 +53,7 @@ export class CryptoService {
|
|
|
52
53
|
if (!Number.isInteger(length) || length <= 0) {
|
|
53
54
|
throw new TypeError("Random byte length must be a positive integer.");
|
|
54
55
|
}
|
|
56
|
+
assertRandomCapability(this.provider);
|
|
55
57
|
try {
|
|
56
58
|
return await this.provider.randomBytes(length);
|
|
57
59
|
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { getDefaultCryptoProvider } from "../../cryptoProvider/cryptoProvider.default.js";
|
|
2
|
+
import { assertHashCapability } from "../../cryptoProvider/cryptoProvider.capability.js";
|
|
2
3
|
import { encode } from "../../cryptoEncoding/cryptoEncoding.core.js";
|
|
3
4
|
import { hashError, rethrowAsCryptoError, } from "../../cryptoErrors/cryptoErrors.helper.js";
|
|
4
5
|
export async function serviceHash(value, provider = getDefaultCryptoProvider()) {
|
|
6
|
+
assertHashCapability(provider);
|
|
5
7
|
try {
|
|
6
8
|
return await provider.hash("sha256", value);
|
|
7
9
|
}
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { getDefaultCryptoProvider } from "../cryptoProvider/cryptoProvider.default.js";
|
|
2
|
+
import { CryptoOperation } from "@zudojs/errors";
|
|
3
|
+
import { assertProviderCapability } from "../cryptoProvider/cryptoProvider.capability.js";
|
|
2
4
|
/**
|
|
3
5
|
* Signs arbitrary data using a private key (PEM text, DER bytes or KeyObject).
|
|
4
6
|
*/
|
|
5
7
|
export async function sign(data, privateKey, options = {}) {
|
|
6
8
|
const provider = options.provider ?? getDefaultCryptoProvider();
|
|
9
|
+
assertProviderCapability(provider, "signing", CryptoOperation.SIGN);
|
|
7
10
|
return provider.sign({
|
|
8
11
|
key: privateKey,
|
|
9
12
|
data,
|
|
@@ -18,6 +21,7 @@ export async function sign(data, privateKey, options = {}) {
|
|
|
18
21
|
*/
|
|
19
22
|
export async function verify(data, signature, publicKey, options = {}) {
|
|
20
23
|
const provider = options.provider ?? getDefaultCryptoProvider();
|
|
24
|
+
assertProviderCapability(provider, "signing", CryptoOperation.VERIFY_SIGNATURE);
|
|
21
25
|
return provider.verify({
|
|
22
26
|
key: publicKey,
|
|
23
27
|
data,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { getDefaultCryptoProvider } from "../cryptoProvider/cryptoProvider.default.js";
|
|
2
|
+
import { assertRandomCapability } from "../cryptoProvider/cryptoProvider.capability.js";
|
|
2
3
|
import { randomInteger } from "../cryptoRandom/cryptoRandom.core.js";
|
|
3
4
|
import { assertBinaryEncoding } from "../cryptoEncoding/cryptoEncoding.core.js";
|
|
4
5
|
import { RANDOM, TOKEN, TOKEN_PREFIX, } from "../cryptoConstants/cryptoConstants.token.js";
|
|
@@ -20,6 +21,7 @@ export async function generateToken(options = {}) {
|
|
|
20
21
|
throw new TypeError("Token prefix must be a string.");
|
|
21
22
|
}
|
|
22
23
|
const provider = options.provider ?? getDefaultCryptoProvider();
|
|
24
|
+
assertRandomCapability(provider);
|
|
23
25
|
const raw = await provider.randomBytes(bytes);
|
|
24
26
|
const token = Buffer.from(raw).toString(encoding);
|
|
25
27
|
return options.prefix ? `${options.prefix}${token}` : token;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { getDefaultCryptoProvider } from "../cryptoProvider/cryptoProvider.default.js";
|
|
2
|
+
import { assertHashCapability } from "../cryptoProvider/cryptoProvider.capability.js";
|
|
2
3
|
import { encode } from "../cryptoEncoding/cryptoEncoding.core.js";
|
|
3
4
|
import { isHex } from "../cryptoEncoding/encoding/cryptoEncoding.hex.js";
|
|
4
5
|
import { timingSafeEqual } from "../compare/compare.helper.js";
|
|
@@ -9,6 +10,7 @@ import { timingSafeEqual } from "../compare/compare.helper.js";
|
|
|
9
10
|
*/
|
|
10
11
|
export async function hashToken(token, provider = getDefaultCryptoProvider()) {
|
|
11
12
|
assertToken(token);
|
|
13
|
+
assertHashCapability(provider);
|
|
12
14
|
const digest = await provider.hash("sha256", token);
|
|
13
15
|
return encode(digest, "hex");
|
|
14
16
|
}
|
|
@@ -17,6 +19,7 @@ export async function hashToken(token, provider = getDefaultCryptoProvider()) {
|
|
|
17
19
|
*/
|
|
18
20
|
export async function hashTokenBase64Url(token, provider = getDefaultCryptoProvider()) {
|
|
19
21
|
assertToken(token);
|
|
22
|
+
assertHashCapability(provider);
|
|
20
23
|
const digest = await provider.hash("sha256", token);
|
|
21
24
|
return encode(digest, "base64url");
|
|
22
25
|
}
|
|
@@ -3,9 +3,10 @@ import type { CryptoInput, PasswordHashProviderOptions } from "../../../cryptoPr
|
|
|
3
3
|
* Hashes a password with scrypt (default) or PBKDF2 and returns the
|
|
4
4
|
* self-describing, versioned encoding produced by `encodePasswordHash`.
|
|
5
5
|
*
|
|
6
|
-
* The encoded string is validated against `PASSWORD_HASH.LIMITS`,
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* The encoded string is validated against `PASSWORD_HASH.LIMITS`, and a
|
|
7
|
+
* scrypt cost below `PASSWORD_HASH.SCRYPT.MIN_COST` (or PBKDF2 iterations
|
|
8
|
+
* below `PBKDF2.MIN_ITERATIONS`) is refused, so the provider does not mint
|
|
9
|
+
* hashes with short salts or tiny work factors.
|
|
9
10
|
*/
|
|
10
11
|
export declare function hashPassword(password: CryptoInput, options?: PasswordHashProviderOptions): Promise<string>;
|
|
11
12
|
/**
|
|
@@ -4,15 +4,17 @@ import { timingSafeEqual } from "../../../compare/compare.helper.js";
|
|
|
4
4
|
import { CryptoAlgorithm } from "../../../cryptoConstants/cryptoConstants.type.js";
|
|
5
5
|
import { PASSWORD_HASH } from "../../../cryptoConstants/cryptoConstants.security.js";
|
|
6
6
|
import { PASSWORD_FORMAT_VERSION, decodePasswordHash, encodePasswordHash, pbkdf2PasswordAlgorithm, } from "../../../cryptoPassword/cryptoPassword.codec.js";
|
|
7
|
+
import { assertNewHashCost } from "../../../cryptoPassword/cryptoPassword.helper.js";
|
|
7
8
|
import { deriveKey } from "./nodeCryptoProvider.derivation.js";
|
|
8
9
|
import { keyDerivationError } from "../../../cryptoErrors/cryptoErrors.helper.js";
|
|
9
10
|
/**
|
|
10
11
|
* Hashes a password with scrypt (default) or PBKDF2 and returns the
|
|
11
12
|
* self-describing, versioned encoding produced by `encodePasswordHash`.
|
|
12
13
|
*
|
|
13
|
-
* The encoded string is validated against `PASSWORD_HASH.LIMITS`,
|
|
14
|
-
*
|
|
15
|
-
*
|
|
14
|
+
* The encoded string is validated against `PASSWORD_HASH.LIMITS`, and a
|
|
15
|
+
* scrypt cost below `PASSWORD_HASH.SCRYPT.MIN_COST` (or PBKDF2 iterations
|
|
16
|
+
* below `PBKDF2.MIN_ITERATIONS`) is refused, so the provider does not mint
|
|
17
|
+
* hashes with short salts or tiny work factors.
|
|
16
18
|
*/
|
|
17
19
|
export async function hashPassword(password, options) {
|
|
18
20
|
const algorithm = options?.algorithm ?? "scrypt";
|
|
@@ -23,7 +25,8 @@ export async function hashPassword(password, options) {
|
|
|
23
25
|
case "scrypt": {
|
|
24
26
|
const cost = options?.memoryCost ?? PASSWORD_HASH.SCRYPT.COST;
|
|
25
27
|
const blockSize = options?.blockSize ?? PASSWORD_HASH.SCRYPT.BLOCK_SIZE;
|
|
26
|
-
const parallelization = options?.parallelism ?? PASSWORD_HASH.SCRYPT.
|
|
28
|
+
const parallelization = options?.parallelism ?? PASSWORD_HASH.SCRYPT.PASSWORD_PARALLELIZATION;
|
|
29
|
+
assertNewHashCost(cost);
|
|
27
30
|
// Validate before deriving so invalid parameters fail fast.
|
|
28
31
|
const placeholder = new Uint8Array(keyBytes);
|
|
29
32
|
encodePasswordHash({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zudojs/crypto",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "Cryptographic primitives for hashing, encryption, tokens, and secure random generation.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -23,8 +23,8 @@
|
|
|
23
23
|
"node": ">=24.0.0"
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"@zudojs/constants": "1.
|
|
27
|
-
"@zudojs/errors": "1.0
|
|
26
|
+
"@zudojs/constants": "1.1.1",
|
|
27
|
+
"@zudojs/errors": "1.2.0"
|
|
28
28
|
},
|
|
29
29
|
"license": "MIT",
|
|
30
30
|
"author": {
|