1'use strict'; 2 3const { 4 ArrayBufferIsView, 5 ArrayBufferPrototypeGetByteLength, 6 ArrayPrototypeIncludes, 7 ArrayPrototypePush, 8 BigInt, 9 DataViewPrototypeGetBuffer, 10 DataViewPrototypeGetByteLength, 11 DataViewPrototypeGetByteOffset, 12 FunctionPrototypeBind, 13 Number, 14 ObjectKeys, 15 ObjectPrototypeHasOwnProperty, 16 Promise, 17 StringPrototypeToUpperCase, 18 Symbol, 19 TypedArrayPrototypeGetBuffer, 20 TypedArrayPrototypeGetByteLength, 21 TypedArrayPrototypeGetByteOffset, 22 TypedArrayPrototypeSlice, 23 Uint8Array, 24} = primordials; 25 26const { 27 getCiphers: _getCiphers, 28 getCurves: _getCurves, 29 getHashes: _getHashes, 30 setEngine: _setEngine, 31 secureHeapUsed: _secureHeapUsed, 32} = internalBinding('crypto'); 33 34const { getOptionValue } = require('internal/options'); 35 36const { 37 crypto: { 38 ENGINE_METHOD_ALL, 39 }, 40} = internalBinding('constants'); 41 42const normalizeHashName = require('internal/crypto/hashnames'); 43 44const { 45 hideStackFrames, 46 codes: { 47 ERR_CRYPTO_CUSTOM_ENGINE_NOT_SUPPORTED, 48 ERR_CRYPTO_ENGINE_UNKNOWN, 49 ERR_INVALID_ARG_TYPE, 50 ERR_INVALID_ARG_VALUE, 51 ERR_OUT_OF_RANGE, 52 }, 53} = require('internal/errors'); 54 55const { 56 validateArray, 57 validateNumber, 58 validateString, 59} = require('internal/validators'); 60 61const { Buffer } = require('buffer'); 62 63const { 64 cachedResult, 65 filterDuplicateStrings, 66 lazyDOMException, 67} = require('internal/util'); 68 69const { 70 isDataView, 71 isArrayBufferView, 72 isAnyArrayBuffer, 73} = require('internal/util/types'); 74 75const kHandle = Symbol('kHandle'); 76const kKeyObject = Symbol('kKeyObject'); 77 78let defaultEncoding = 'buffer'; 79 80function setDefaultEncoding(val) { 81 defaultEncoding = val; 82} 83 84function getDefaultEncoding() { 85 return defaultEncoding; 86} 87 88// This is here because many functions accepted binary strings without 89// any explicit encoding in older versions of node, and we don't want 90// to break them unnecessarily. 91function toBuf(val, encoding) { 92 if (typeof val === 'string') { 93 if (encoding === 'buffer') 94 encoding = 'utf8'; 95 return Buffer.from(val, encoding); 96 } 97 return val; 98} 99 100const getCiphers = cachedResult(() => filterDuplicateStrings(_getCiphers())); 101const getHashes = cachedResult(() => filterDuplicateStrings(_getHashes())); 102const getCurves = cachedResult(() => filterDuplicateStrings(_getCurves())); 103 104function setEngine(id, flags) { 105 validateString(id, 'id'); 106 if (flags) 107 validateNumber(flags, 'flags'); 108 flags = flags >>> 0; 109 110 // Use provided engine for everything by default 111 if (flags === 0) 112 flags = ENGINE_METHOD_ALL; 113 114 if (typeof _setEngine !== 'function') 115 throw new ERR_CRYPTO_CUSTOM_ENGINE_NOT_SUPPORTED(); 116 if (!_setEngine(id, flags)) 117 throw new ERR_CRYPTO_ENGINE_UNKNOWN(id); 118} 119 120const getArrayBufferOrView = hideStackFrames((buffer, name, encoding) => { 121 if (isAnyArrayBuffer(buffer)) 122 return buffer; 123 if (typeof buffer === 'string') { 124 if (encoding === 'buffer') 125 encoding = 'utf8'; 126 return Buffer.from(buffer, encoding); 127 } 128 if (!isArrayBufferView(buffer)) { 129 throw new ERR_INVALID_ARG_TYPE( 130 name, 131 [ 132 'string', 133 'ArrayBuffer', 134 'Buffer', 135 'TypedArray', 136 'DataView', 137 ], 138 buffer, 139 ); 140 } 141 return buffer; 142}); 143 144// The maximum buffer size that we'll support in the WebCrypto impl 145const kMaxBufferLength = (2 ** 31) - 1; 146 147// The EC named curves that we currently support via the Web Crypto API. 148const kNamedCurveAliases = { 149 'P-256': 'prime256v1', 150 'P-384': 'secp384r1', 151 'P-521': 'secp521r1', 152}; 153 154const kAesKeyLengths = [128, 192, 256]; 155 156// These are the only hash algorithms we currently support via 157// the Web Crypto API. 158const kHashTypes = [ 159 'SHA-1', 160 'SHA-256', 161 'SHA-384', 162 'SHA-512', 163]; 164 165const kSupportedAlgorithms = { 166 'digest': { 167 'SHA-1': null, 168 'SHA-256': null, 169 'SHA-384': null, 170 'SHA-512': null, 171 }, 172 'generateKey': { 173 'RSASSA-PKCS1-v1_5': 'RsaHashedKeyGenParams', 174 'RSA-PSS': 'RsaHashedKeyGenParams', 175 'RSA-OAEP': 'RsaHashedKeyGenParams', 176 'ECDSA': 'EcKeyGenParams', 177 'ECDH': 'EcKeyGenParams', 178 'AES-CTR': 'AesKeyGenParams', 179 'AES-CBC': 'AesKeyGenParams', 180 'AES-GCM': 'AesKeyGenParams', 181 'AES-KW': 'AesKeyGenParams', 182 'HMAC': 'HmacKeyGenParams', 183 'X25519': null, 184 'Ed25519': null, 185 'X448': null, 186 'Ed448': null, 187 }, 188 'sign': { 189 'RSASSA-PKCS1-v1_5': null, 190 'RSA-PSS': 'RsaPssParams', 191 'ECDSA': 'EcdsaParams', 192 'HMAC': null, 193 'Ed25519': null, 194 'Ed448': 'Ed448Params', 195 }, 196 'verify': { 197 'RSASSA-PKCS1-v1_5': null, 198 'RSA-PSS': 'RsaPssParams', 199 'ECDSA': 'EcdsaParams', 200 'HMAC': null, 201 'Ed25519': null, 202 'Ed448': 'Ed448Params', 203 }, 204 'importKey': { 205 'RSASSA-PKCS1-v1_5': 'RsaHashedImportParams', 206 'RSA-PSS': 'RsaHashedImportParams', 207 'RSA-OAEP': 'RsaHashedImportParams', 208 'ECDSA': 'EcKeyImportParams', 209 'ECDH': 'EcKeyImportParams', 210 'HMAC': 'HmacImportParams', 211 'HKDF': null, 212 'PBKDF2': null, 213 'AES-CTR': null, 214 'AES-CBC': null, 215 'AES-GCM': null, 216 'AES-KW': null, 217 'Ed25519': null, 218 'X25519': null, 219 'Ed448': null, 220 'X448': null, 221 }, 222 'deriveBits': { 223 'HKDF': 'HkdfParams', 224 'PBKDF2': 'Pbkdf2Params', 225 'ECDH': 'EcdhKeyDeriveParams', 226 'X25519': 'EcdhKeyDeriveParams', 227 'X448': 'EcdhKeyDeriveParams', 228 }, 229 'encrypt': { 230 'RSA-OAEP': 'RsaOaepParams', 231 'AES-CBC': 'AesCbcParams', 232 'AES-GCM': 'AesGcmParams', 233 'AES-CTR': 'AesCtrParams', 234 }, 235 'decrypt': { 236 'RSA-OAEP': 'RsaOaepParams', 237 'AES-CBC': 'AesCbcParams', 238 'AES-GCM': 'AesGcmParams', 239 'AES-CTR': 'AesCtrParams', 240 }, 241 'get key length': { 242 'AES-CBC': 'AesDerivedKeyParams', 243 'AES-CTR': 'AesDerivedKeyParams', 244 'AES-GCM': 'AesDerivedKeyParams', 245 'AES-KW': 'AesDerivedKeyParams', 246 'HMAC': 'HmacImportParams', 247 'HKDF': null, 248 'PBKDF2': null, 249 }, 250 'wrapKey': { 251 'AES-KW': null, 252 }, 253 'unwrapKey': { 254 'AES-KW': null, 255 }, 256}; 257 258const simpleAlgorithmDictionaries = { 259 AesGcmParams: { iv: 'BufferSource', additionalData: 'BufferSource' }, 260 RsaHashedKeyGenParams: { hash: 'HashAlgorithmIdentifier' }, 261 EcKeyGenParams: {}, 262 HmacKeyGenParams: { hash: 'HashAlgorithmIdentifier' }, 263 RsaPssParams: {}, 264 EcdsaParams: { hash: 'HashAlgorithmIdentifier' }, 265 HmacImportParams: { hash: 'HashAlgorithmIdentifier' }, 266 HkdfParams: { 267 hash: 'HashAlgorithmIdentifier', 268 salt: 'BufferSource', 269 info: 'BufferSource', 270 }, 271 Ed448Params: { context: 'BufferSource' }, 272 Pbkdf2Params: { hash: 'HashAlgorithmIdentifier', salt: 'BufferSource' }, 273 RsaOaepParams: { label: 'BufferSource' }, 274 RsaHashedImportParams: { hash: 'HashAlgorithmIdentifier' }, 275 EcKeyImportParams: {}, 276}; 277 278function validateMaxBufferLength(data, name) { 279 if (data.byteLength > kMaxBufferLength) { 280 throw lazyDOMException( 281 `${name} must be less than ${kMaxBufferLength + 1} bits`, 282 'OperationError'); 283 } 284} 285 286let webidl; 287 288// https://w3c.github.io/webcrypto/#algorithm-normalization-normalize-an-algorithm 289// adapted for Node.js from Deno's implementation 290// https://github.com/denoland/deno/blob/v1.29.1/ext/crypto/00_crypto.js#L195 291function normalizeAlgorithm(algorithm, op) { 292 if (typeof algorithm === 'string') 293 return normalizeAlgorithm({ name: algorithm }, op); 294 295 webidl ??= require('internal/crypto/webidl'); 296 297 // 1. 298 const registeredAlgorithms = kSupportedAlgorithms[op]; 299 // 2. 3. 300 const initialAlg = webidl.converters.Algorithm(algorithm, { 301 prefix: 'Failed to normalize algorithm', 302 context: 'passed algorithm', 303 }); 304 // 4. 305 let algName = initialAlg.name; 306 307 // 5. 308 let desiredType; 309 for (const key in registeredAlgorithms) { 310 if (!ObjectPrototypeHasOwnProperty(registeredAlgorithms, key)) { 311 continue; 312 } 313 if ( 314 StringPrototypeToUpperCase(key) === StringPrototypeToUpperCase(algName) 315 ) { 316 algName = key; 317 desiredType = registeredAlgorithms[key]; 318 } 319 } 320 if (desiredType === undefined) 321 throw lazyDOMException('Unrecognized algorithm name', 'NotSupportedError'); 322 323 // Fast path everything below if the registered dictionary is null 324 if (desiredType === null) 325 return { name: algName }; 326 327 // 6. 328 const normalizedAlgorithm = webidl.converters[desiredType](algorithm, { 329 prefix: 'Failed to normalize algorithm', 330 context: 'passed algorithm', 331 }); 332 // 7. 333 normalizedAlgorithm.name = algName; 334 335 // 9. 336 const dict = simpleAlgorithmDictionaries[desiredType]; 337 // 10. 338 const dictKeys = dict ? ObjectKeys(dict) : []; 339 for (let i = 0; i < dictKeys.length; i++) { 340 const member = dictKeys[i]; 341 if (!ObjectPrototypeHasOwnProperty(dict, member)) 342 continue; 343 const idlType = dict[member]; 344 const idlValue = normalizedAlgorithm[member]; 345 // 3. 346 if (idlType === 'BufferSource' && idlValue) { 347 const isView = ArrayBufferIsView(idlValue); 348 normalizedAlgorithm[member] = TypedArrayPrototypeSlice( 349 new Uint8Array( 350 isView ? getDataViewOrTypedArrayBuffer(idlValue) : idlValue, 351 isView ? getDataViewOrTypedArrayByteOffset(idlValue) : 0, 352 isView ? getDataViewOrTypedArrayByteLength(idlValue) : ArrayBufferPrototypeGetByteLength(idlValue), 353 ), 354 ); 355 } else if (idlType === 'HashAlgorithmIdentifier') { 356 normalizedAlgorithm[member] = normalizeAlgorithm(idlValue, 'digest'); 357 } else if (idlType === 'AlgorithmIdentifier') { 358 // This extension point is not used by any supported algorithm (yet?) 359 throw lazyDOMException('Not implemented.', 'NotSupportedError'); 360 } 361 } 362 363 return normalizedAlgorithm; 364} 365 366function getDataViewOrTypedArrayBuffer(V) { 367 return isDataView(V) ? 368 DataViewPrototypeGetBuffer(V) : TypedArrayPrototypeGetBuffer(V); 369} 370 371function getDataViewOrTypedArrayByteOffset(V) { 372 return isDataView(V) ? 373 DataViewPrototypeGetByteOffset(V) : TypedArrayPrototypeGetByteOffset(V); 374} 375 376function getDataViewOrTypedArrayByteLength(V) { 377 return isDataView(V) ? 378 DataViewPrototypeGetByteLength(V) : TypedArrayPrototypeGetByteLength(V); 379} 380 381function hasAnyNotIn(set, checks) { 382 for (const s of set) 383 if (!ArrayPrototypeIncludes(checks, s)) 384 return true; 385 return false; 386} 387 388function validateBitLength(length, name, required = false) { 389 if (length !== undefined || required) { 390 validateNumber(length, name); 391 if (length < 0) 392 throw new ERR_OUT_OF_RANGE(name, '> 0'); 393 if (length % 8) { 394 throw new ERR_INVALID_ARG_VALUE( 395 name, 396 length, 397 'must be a multiple of 8'); 398 } 399 } 400} 401 402function validateByteLength(buf, name, target) { 403 if (buf.byteLength !== target) { 404 throw lazyDOMException( 405 `${name} must contain exactly ${target} bytes`, 406 'OperationError'); 407 } 408} 409 410const validateByteSource = hideStackFrames((val, name) => { 411 val = toBuf(val); 412 413 if (isAnyArrayBuffer(val) || isArrayBufferView(val)) 414 return val; 415 416 throw new ERR_INVALID_ARG_TYPE( 417 name, 418 [ 419 'string', 420 'ArrayBuffer', 421 'TypedArray', 422 'DataView', 423 'Buffer', 424 ], 425 val); 426}); 427 428function onDone(resolve, reject, err, result) { 429 if (err) { 430 return reject(lazyDOMException( 431 'The operation failed for an operation-specific reason', 432 { name: 'OperationError', cause: err })); 433 } 434 resolve(result); 435} 436 437function jobPromise(getJob) { 438 return new Promise((resolve, reject) => { 439 try { 440 const job = getJob(); 441 job.ondone = FunctionPrototypeBind(onDone, job, resolve, reject); 442 job.run(); 443 } catch (err) { 444 onDone(resolve, reject, err); 445 } 446 }); 447} 448 449// In WebCrypto, the publicExponent option in RSA is represented as a 450// WebIDL "BigInteger"... that is, a Uint8Array that allows an arbitrary 451// number of leading zero bits. Our conventional APIs for reading 452// an unsigned int from a Buffer are not adequate. The implementation 453// here is adapted from the chromium implementation here: 454// https://github.com/chromium/chromium/blob/HEAD/third_party/blink/public/platform/web_crypto_algorithm_params.h, but ported to JavaScript 455// Returns undefined if the conversion was unsuccessful. 456function bigIntArrayToUnsignedInt(input) { 457 let result = 0; 458 459 for (let n = 0; n < input.length; ++n) { 460 const n_reversed = input.length - n - 1; 461 if (n_reversed >= 4 && input[n]) 462 return; // Too large 463 result |= input[n] << 8 * n_reversed; 464 } 465 466 return result; 467} 468 469function bigIntArrayToUnsignedBigInt(input) { 470 let result = 0n; 471 472 for (let n = 0; n < input.length; ++n) { 473 const n_reversed = input.length - n - 1; 474 result |= BigInt(input[n]) << 8n * BigInt(n_reversed); 475 } 476 477 return result; 478} 479 480function getStringOption(options, key) { 481 let value; 482 if (options && (value = options[key]) != null) 483 validateString(value, `options.${key}`); 484 return value; 485} 486 487function getUsagesUnion(usageSet, ...usages) { 488 const newset = []; 489 for (let n = 0; n < usages.length; n++) { 490 if (usageSet.has(usages[n])) 491 ArrayPrototypePush(newset, usages[n]); 492 } 493 return newset; 494} 495 496function getBlockSize(name) { 497 switch (name) { 498 case 'SHA-1': return 512; 499 case 'SHA-256': return 512; 500 case 'SHA-384': return 1024; 501 case 'SHA-512': return 1024; 502 } 503} 504 505const kKeyOps = { 506 sign: 1, 507 verify: 2, 508 encrypt: 3, 509 decrypt: 4, 510 wrapKey: 5, 511 unwrapKey: 6, 512 deriveKey: 7, 513 deriveBits: 8, 514}; 515 516function validateKeyOps(keyOps, usagesSet) { 517 if (keyOps === undefined) return; 518 validateArray(keyOps, 'keyData.key_ops'); 519 let flags = 0; 520 for (let n = 0; n < keyOps.length; n++) { 521 const op = keyOps[n]; 522 const op_flag = kKeyOps[op]; 523 // Skipping unknown key ops 524 if (op_flag === undefined) 525 continue; 526 // Have we seen it already? if so, error 527 if (flags & (1 << op_flag)) 528 throw lazyDOMException('Duplicate key operation', 'DataError'); 529 flags |= (1 << op_flag); 530 531 // TODO(@jasnell): RFC7517 section 4.3 strong recommends validating 532 // key usage combinations. Specifically, it says that unrelated key 533 // ops SHOULD NOT be used together. We're not yet validating that here. 534 } 535 536 if (usagesSet !== undefined) { 537 for (const use of usagesSet) { 538 if (!ArrayPrototypeIncludes(keyOps, use)) { 539 throw lazyDOMException( 540 'Key operations and usage mismatch', 541 'DataError'); 542 } 543 } 544 } 545} 546 547function secureHeapUsed() { 548 const val = _secureHeapUsed(); 549 if (val === undefined) 550 return { total: 0, used: 0, utilization: 0, min: 0 }; 551 const used = Number(_secureHeapUsed()); 552 const total = Number(getOptionValue('--secure-heap')); 553 const min = Number(getOptionValue('--secure-heap-min')); 554 const utilization = used / total; 555 return { total, used, utilization, min }; 556} 557 558module.exports = { 559 getArrayBufferOrView, 560 getCiphers, 561 getCurves, 562 getDataViewOrTypedArrayBuffer, 563 getDefaultEncoding, 564 getHashes, 565 kHandle, 566 kKeyObject, 567 setDefaultEncoding, 568 setEngine, 569 toBuf, 570 571 kHashTypes, 572 kNamedCurveAliases, 573 kAesKeyLengths, 574 normalizeAlgorithm, 575 normalizeHashName, 576 hasAnyNotIn, 577 validateBitLength, 578 validateByteLength, 579 validateByteSource, 580 validateKeyOps, 581 jobPromise, 582 validateMaxBufferLength, 583 bigIntArrayToUnsignedBigInt, 584 bigIntArrayToUnsignedInt, 585 getBlockSize, 586 getStringOption, 587 getUsagesUnion, 588 secureHeapUsed, 589}; 590