| 'use strict'; |
| |
| const { |
| ArrayPrototypeIncludes, |
| ArrayPrototypePush, |
| BigInt, |
| FunctionPrototypeBind, |
| Number, |
| ObjectKeys, |
| Promise, |
| StringPrototypeToLowerCase, |
| Symbol, |
| } = primordials; |
| |
| const { |
| getCiphers: _getCiphers, |
| getCurves: _getCurves, |
| getHashes: _getHashes, |
| setEngine: _setEngine, |
| secureHeapUsed: _secureHeapUsed, |
| } = internalBinding('crypto'); |
| |
| const { getOptionValue } = require('internal/options'); |
| |
| const { |
| crypto: { |
| ENGINE_METHOD_ALL |
| } |
| } = internalBinding('constants'); |
| |
| const normalizeHashName = require('internal/crypto/hashnames'); |
| |
| const { |
| hideStackFrames, |
| codes: { |
| ERR_CRYPTO_ENGINE_UNKNOWN, |
| ERR_INVALID_ARG_TYPE, |
| ERR_INVALID_ARG_VALUE, |
| ERR_OUT_OF_RANGE, |
| } |
| } = require('internal/errors'); |
| |
| const { |
| validateArray, |
| validateNumber, |
| validateString |
| } = require('internal/validators'); |
| |
| const { Buffer } = require('buffer'); |
| |
| const { |
| cachedResult, |
| filterDuplicateStrings, |
| lazyDOMException, |
| } = require('internal/util'); |
| |
| const { |
| isArrayBufferView, |
| isAnyArrayBuffer, |
| } = require('internal/util/types'); |
| |
| const kHandle = Symbol('kHandle'); |
| const kKeyObject = Symbol('kKeyObject'); |
| |
| const lazyRequireCache = {}; |
| |
| function lazyRequire(name) { |
| let ret = lazyRequireCache[name]; |
| if (ret === undefined) |
| ret = lazyRequireCache[name] = require(name); |
| return ret; |
| } |
| |
| let defaultEncoding = 'buffer'; |
| |
| function setDefaultEncoding(val) { |
| defaultEncoding = val; |
| } |
| |
| function getDefaultEncoding() { |
| return defaultEncoding; |
| } |
| |
| // This is here because many functions accepted binary strings without |
| // any explicit encoding in older versions of node, and we don't want |
| // to break them unnecessarily. |
| function toBuf(val, encoding) { |
| if (typeof val === 'string') { |
| if (encoding === 'buffer') |
| encoding = 'utf8'; |
| return Buffer.from(val, encoding); |
| } |
| return val; |
| } |
| |
| const getCiphers = cachedResult(() => filterDuplicateStrings(_getCiphers())); |
| const getHashes = cachedResult(() => filterDuplicateStrings(_getHashes())); |
| const getCurves = cachedResult(() => filterDuplicateStrings(_getCurves())); |
| |
| function setEngine(id, flags) { |
| validateString(id, 'id'); |
| if (flags) |
| validateNumber(flags, 'flags'); |
| flags = flags >>> 0; |
| |
| // Use provided engine for everything by default |
| if (flags === 0) |
| flags = ENGINE_METHOD_ALL; |
| |
| if (!_setEngine(id, flags)) |
| throw new ERR_CRYPTO_ENGINE_UNKNOWN(id); |
| } |
| |
| const getArrayBufferOrView = hideStackFrames((buffer, name, encoding) => { |
| if (isAnyArrayBuffer(buffer)) |
| return buffer; |
| if (typeof buffer === 'string') { |
| if (encoding === 'buffer') |
| encoding = 'utf8'; |
| return Buffer.from(buffer, encoding); |
| } |
| if (!isArrayBufferView(buffer)) { |
| throw new ERR_INVALID_ARG_TYPE( |
| name, |
| [ |
| 'string', |
| 'ArrayBuffer', |
| 'Buffer', |
| 'TypedArray', |
| 'DataView', |
| ], |
| buffer |
| ); |
| } |
| return buffer; |
| }); |
| |
| // The maximum buffer size that we'll support in the WebCrypto impl |
| const kMaxBufferLength = (2 ** 31) - 1; |
| |
| // The EC named curves that we currently support via the Web Crypto API. |
| const kNamedCurveAliases = { |
| 'P-256': 'prime256v1', |
| 'P-384': 'secp384r1', |
| 'P-521': 'secp521r1', |
| 'NODE-ED25519': 'ed25519', |
| 'NODE-ED448': 'ed448', |
| 'NODE-X25519': 'x25519', |
| 'NODE-X448': 'x448', |
| }; |
| |
| const kAesKeyLengths = [128, 192, 256]; |
| |
| // These are the only algorithms we currently support |
| // via the Web Crypto API |
| const kAlgorithms = { |
| 'rsassa-pkcs1-v1_5': 'RSASSA-PKCS1-v1_5', |
| 'rsa-pss': 'RSA-PSS', |
| 'rsa-oaep': 'RSA-OAEP', |
| 'ecdsa': 'ECDSA', |
| 'ecdh': 'ECDH', |
| 'aes-ctr': 'AES-CTR', |
| 'aes-cbc': 'AES-CBC', |
| 'aes-gcm': 'AES-GCM', |
| 'aes-kw': 'AES-KW', |
| 'hmac': 'HMAC', |
| 'sha-1': 'SHA-1', |
| 'sha-256': 'SHA-256', |
| 'sha-384': 'SHA-384', |
| 'sha-512': 'SHA-512', |
| 'hkdf': 'HKDF', |
| 'pbkdf2': 'PBKDF2', |
| // Following here are Node.js specific extensions. All |
| // should be prefixed with 'node-' |
| 'node-dsa': 'NODE-DSA', |
| 'node-dh': 'NODE-DH', |
| 'node-scrypt': 'NODE-SCRYPT', |
| 'node-ed25519': 'NODE-ED25519', |
| 'node-ed448': 'NODE-ED448', |
| }; |
| const kAlgorithmsKeys = ObjectKeys(kAlgorithms); |
| |
| // These are the only export and import formats we currently |
| // support via the Web Crypto API |
| const kExportFormats = [ |
| 'raw', |
| 'pkcs8', |
| 'spki', |
| 'jwk', |
| 'node.keyObject']; |
| |
| // These are the only hash algorithms we currently support via |
| // the Web Crypto API. |
| const kHashTypes = [ |
| 'SHA-1', |
| 'SHA-256', |
| 'SHA-384', |
| 'SHA-512', |
| ]; |
| |
| function validateMaxBufferLength(data, name) { |
| if (data.byteLength > kMaxBufferLength) { |
| throw lazyDOMException( |
| `${name} must be less than ${kMaxBufferLength + 1} bits`, |
| 'OperationError'); |
| } |
| } |
| |
| function normalizeAlgorithm(algorithm) { |
| if (algorithm != null) { |
| if (typeof algorithm === 'string') |
| algorithm = { name: algorithm }; |
| |
| if (typeof algorithm === 'object') { |
| const { name } = algorithm; |
| if (typeof name !== 'string' || |
| !ArrayPrototypeIncludes( |
| kAlgorithmsKeys, |
| StringPrototypeToLowerCase(name))) { |
| throw lazyDOMException('Unrecognized name.', 'NotSupportedError'); |
| } |
| let { hash } = algorithm; |
| if (hash !== undefined) { |
| hash = normalizeAlgorithm(hash); |
| if (!ArrayPrototypeIncludes(kHashTypes, hash.name)) |
| throw lazyDOMException('Unrecognized name.', 'NotSupportedError'); |
| } |
| const normalized = { |
| ...algorithm, |
| name: kAlgorithms[StringPrototypeToLowerCase(name)], |
| }; |
| if (hash) { |
| normalized.hash = hash; |
| } |
| return normalized; |
| } |
| } |
| throw lazyDOMException('Unrecognized name.', 'NotSupportedError'); |
| } |
| |
| function hasAnyNotIn(set, checks) { |
| for (const s of set) |
| if (!ArrayPrototypeIncludes(checks, s)) |
| return true; |
| return false; |
| } |
| |
| function validateBitLength(length, name, required = false) { |
| if (length !== undefined || required) { |
| validateNumber(length, name); |
| if (length < 0) |
| throw new ERR_OUT_OF_RANGE(name, '> 0'); |
| if (length % 8) { |
| throw new ERR_INVALID_ARG_VALUE( |
| name, |
| length, |
| 'must be a multiple of 8'); |
| } |
| } |
| } |
| |
| function validateByteLength(buf, name, target) { |
| if (buf.byteLength !== target) { |
| throw lazyDOMException( |
| `${name} must contain exactly ${target} bytes`, |
| 'OperationError'); |
| } |
| } |
| |
| const validateByteSource = hideStackFrames((val, name) => { |
| val = toBuf(val); |
| |
| if (isAnyArrayBuffer(val) || isArrayBufferView(val)) |
| return; |
| |
| throw new ERR_INVALID_ARG_TYPE( |
| name, |
| [ |
| 'string', |
| 'ArrayBuffer', |
| 'TypedArray', |
| 'DataView', |
| 'Buffer', |
| ], |
| val); |
| }); |
| |
| function onDone(resolve, reject, err, result) { |
| if (err) return reject(err); |
| resolve(result); |
| } |
| |
| function jobPromise(job) { |
| return new Promise((resolve, reject) => { |
| job.ondone = FunctionPrototypeBind(onDone, job, resolve, reject); |
| job.run(); |
| }); |
| } |
| |
| // In WebCrypto, the publicExponent option in RSA is represented as a |
| // WebIDL "BigInteger"... that is, a Uint8Array that allows an arbitrary |
| // number of leading zero bits. Our conventional APIs for reading |
| // an unsigned int from a Buffer are not adequate. The implementation |
| // here is adapted from the chromium implementation here: |
| // https://github.com/chromium/chromium/blob/HEAD/third_party/blink/public/platform/web_crypto_algorithm_params.h, but ported to JavaScript |
| // Returns undefined if the conversion was unsuccessful. |
| function bigIntArrayToUnsignedInt(input) { |
| let result = 0; |
| |
| for (let n = 0; n < input.length; ++n) { |
| const n_reversed = input.length - n - 1; |
| if (n_reversed >= 4 && input[n]) |
| return; // Too large |
| result |= input[n] << 8 * n_reversed; |
| } |
| |
| return result; |
| } |
| |
| function bigIntArrayToUnsignedBigInt(input) { |
| let result = 0n; |
| |
| for (let n = 0; n < input.length; ++n) { |
| const n_reversed = input.length - n - 1; |
| result |= BigInt(input[n]) << 8n * BigInt(n_reversed); |
| } |
| |
| return result; |
| } |
| |
| function getStringOption(options, key) { |
| let value; |
| if (options && (value = options[key]) != null) |
| validateString(value, `options.${key}`); |
| return value; |
| } |
| |
| function getUsagesUnion(usageSet, ...usages) { |
| const newset = []; |
| for (let n = 0; n < usages.length; n++) { |
| if (usageSet.has(usages[n])) |
| ArrayPrototypePush(newset, usages[n]); |
| } |
| return newset; |
| } |
| |
| function getHashLength(name) { |
| switch (name) { |
| case 'SHA-1': return 160; |
| case 'SHA-256': return 256; |
| case 'SHA-384': return 384; |
| case 'SHA-512': return 512; |
| } |
| } |
| |
| const kKeyOps = { |
| sign: 1, |
| verify: 2, |
| encrypt: 3, |
| decrypt: 4, |
| wrapKey: 5, |
| unwrapKey: 6, |
| deriveKey: 7, |
| deriveBits: 8, |
| }; |
| |
| function validateKeyOps(keyOps, usagesSet) { |
| if (keyOps === undefined) return; |
| validateArray(keyOps, 'keyData.key_ops'); |
| let flags = 0; |
| for (let n = 0; n < keyOps.length; n++) { |
| const op = keyOps[n]; |
| const op_flag = kKeyOps[op]; |
| // Skipping unknown key ops |
| if (op_flag === undefined) |
| continue; |
| // Have we seen it already? if so, error |
| if (flags & (1 << op_flag)) |
| throw lazyDOMException('Duplicate key operation', 'DataError'); |
| flags |= (1 << op_flag); |
| |
| // TODO(@jasnell): RFC7517 section 4.3 strong recommends validating |
| // key usage combinations. Specifically, it says that unrelated key |
| // ops SHOULD NOT be used together. We're not yet validating that here. |
| } |
| |
| if (usagesSet !== undefined) { |
| for (const use of usagesSet) { |
| if (!ArrayPrototypeIncludes(keyOps, use)) { |
| throw lazyDOMException( |
| 'Key operations and usage mismatch', |
| 'DataError'); |
| } |
| } |
| } |
| } |
| |
| function secureHeapUsed() { |
| const val = _secureHeapUsed(); |
| if (val === undefined) |
| return { total: 0, used: 0, utilization: 0, min: 0 }; |
| const used = Number(_secureHeapUsed()); |
| const total = Number(getOptionValue('--secure-heap')); |
| const min = Number(getOptionValue('--secure-heap-min')); |
| const utilization = used / total; |
| return { total, used, utilization, min }; |
| } |
| |
| module.exports = { |
| getArrayBufferOrView, |
| getCiphers, |
| getCurves, |
| getDefaultEncoding, |
| getHashes, |
| kHandle, |
| kKeyObject, |
| setDefaultEncoding, |
| setEngine, |
| toBuf, |
| |
| kHashTypes, |
| kNamedCurveAliases, |
| kAesKeyLengths, |
| kExportFormats, |
| normalizeAlgorithm, |
| normalizeHashName, |
| hasAnyNotIn, |
| validateBitLength, |
| validateByteLength, |
| validateByteSource, |
| validateKeyOps, |
| jobPromise, |
| lazyRequire, |
| validateMaxBufferLength, |
| bigIntArrayToUnsignedBigInt, |
| bigIntArrayToUnsignedInt, |
| getStringOption, |
| getUsagesUnion, |
| getHashLength, |
| secureHeapUsed, |
| }; |