Skip to content
File

Blob: src/node/internal/crypto_scrypt.ts

typescript213 lines
1// Copyright (c) 2017-2023 Cloudflare, Inc.
2// Licensed under the Apache 2.0 license found in the LICENSE file or at:
3// https://opensource.org/licenses/Apache-2.0
4//
5// Copyright Joyent, Inc. and other Node contributors.
6//
7// Permission is hereby granted, free of charge, to any person obtaining a
8// copy of this software and associated documentation files (the
9// "Software"), to deal in the Software without restriction, including
10// without limitation the rights to use, copy, modify, merge, publish,
11// distribute, sublicense, and/or sell copies of the Software, and to permit
12// persons to whom the Software is furnished to do so, subject to the
13// following conditions:
14//
15// The above copyright notice and this permission notice shall be included
16// in all copies or substantial portions of the Software.
17//
18// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
19// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
21// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
22// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
23// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
24// USE OR OTHER DEALINGS IN THE SOFTWARE.
25 
26import { default as cryptoImpl } from 'node-internal:crypto';
27 
28import {
29 validateFunction,
30 validateInteger,
31 validateUint32,
32} from 'node-internal:validators';
33 
34import { Buffer } from 'node-internal:internal_buffer';
35 
36type ArrayLike = cryptoImpl.ArrayLike;
37 
38import { kMaxLength } from 'node-internal:internal_buffer';
39 
40import { getArrayBufferOrView } from 'node-internal:crypto_util';
41 
42import { ERR_INVALID_ARG_VALUE } from 'node-internal:internal_errors';
43 
44export interface ValidatedScryptOptions {
45 password: ArrayLike;
46 salt: ArrayLike;
47 keylen: number;
48 N: number;
49 r: number;
50 p: number;
51 maxmem: number;
52}
53 
54export interface ScryptOptions {
55 N?: number;
56 r?: number;
57 p?: number;
58 maxmem?: number;
59 cost?: number;
60 blockSize?: number;
61 parallelization?: number;
62}
63 
64const defaults: ScryptOptions = {
65 N: 16384,
66 r: 8,
67 p: 1,
68 maxmem: 32 << 20,
69};
70 
71function validateParameters(
72 password: ArrayLike,
73 salt: ArrayLike,
74 keylen: number,
75 options?: ScryptOptions
76): ValidatedScryptOptions {
77 // TODO(soon): Add support for KeyObject input.
78 password = getArrayBufferOrView(password, 'password');
79 salt = getArrayBufferOrView(salt, 'salt');
80 
81 if (password.byteLength >= kMaxLength) {
82 throw new ERR_INVALID_ARG_VALUE('password', password, 'is too big');
83 }
84 if (salt.byteLength >= kMaxLength) {
85 throw new ERR_INVALID_ARG_VALUE('salt', salt, 'is too big');
86 }
87 
88 validateInteger(keylen, 'keylen', 0, kMaxLength);
89 
90 let { N, r, p, maxmem } = defaults;
91 if (options && options !== defaults) {
92 const has_N = options.N !== undefined;
93 if (has_N) {
94 N = options.N;
95 validateUint32(N, 'N');
96 }
97 if (options.cost !== undefined) {
98 if (has_N) throw new ERR_INVALID_ARG_VALUE('cost', options.cost);
99 N = options.cost;
100 validateUint32(N, 'cost');
101 }
102 const has_r = options.r !== undefined;
103 if (has_r) {
104 r = options.r;
105 validateUint32(r, 'r');
106 }
107 if (options.blockSize !== undefined) {
108 if (has_r)
109 throw new ERR_INVALID_ARG_VALUE('blockSize', options.blockSize);
110 r = options.blockSize;
111 validateUint32(r, 'blockSize');
112 }
113 const has_p = options.p !== undefined;
114 if (has_p) {
115 p = options.p;
116 validateUint32(p, 'p');
117 }
118 if (options.parallelization !== undefined) {
119 if (has_p)
120 throw new ERR_INVALID_ARG_VALUE(
121 'parallelization',
122 options.parallelization
123 );
124 p = options.parallelization;
125 validateUint32(p, 'parallelization');
126 }
127 if (options.maxmem !== undefined) {
128 maxmem = options.maxmem;
129 validateInteger(maxmem, 'maxmem', 0);
130 }
131 if (N === 0) N = defaults.N;
132 if (r === 0) r = defaults.r;
133 if (p === 0) p = defaults.p;
134 if (maxmem === 0) maxmem = defaults.maxmem;
135 }
136 
137 return {
138 password,
139 salt,
140 keylen,
141 N: N as number,
142 r: r as number,
143 p: p as number,
144 maxmem: maxmem as number,
145 };
146}
147 
148type Callback = (err: Error | null, derivedKey?: Buffer) => void;
149type OptionsOrCallback = ScryptOptions | Callback;
150 
151export function scrypt(
152 password: ArrayLike,
153 salt: ArrayLike,
154 keylen: number,
155 options: OptionsOrCallback,
156 callback: OptionsOrCallback = defaults
157): void {
158 if (callback === defaults) {
159 callback = options;
160 options = defaults;
161 }
162 
163 let N: number;
164 let r: number;
165 let p: number;
166 let maxmem: number;
167 ({ password, salt, keylen, N, r, p, maxmem } = validateParameters(
168 password,
169 salt,
170 keylen,
171 options as ScryptOptions
172 ));
173 
174 validateFunction(callback, 'callback');
175 
176 new Promise<ArrayBuffer>((res, rej) => {
177 try {
178 res(cryptoImpl.getScrypt(password, salt, N, r, p, maxmem, keylen));
179 } catch (err) {
180 rej(err as Error);
181 }
182 }).then(
183 (val: ArrayBuffer) => {
184 (callback as Callback)(null, Buffer.from(val));
185 },
186 (err: unknown) => {
187 (callback as Callback)(err as Error);
188 }
189 );
190}
191 
192export function scryptSync(
193 password: ArrayLike,
194 salt: ArrayLike,
195 keylen: number,
196 options: ScryptOptions
197): Buffer {
198 let N: number;
199 let r: number;
200 let p: number;
201 let maxmem: number;
202 ({ password, salt, keylen, N, r, p, maxmem } = validateParameters(
203 password,
204 salt,
205 keylen,
206 options
207 ));
208 
209 return Buffer.from(
210 cryptoImpl.getScrypt(password, salt, N, r, p, maxmem, keylen)
211 );
212}