diff --git a/packages/pg/lib/crypto/utils-legacy.js b/packages/pg/lib/crypto/utils-legacy.js deleted file mode 100644 index d70fdb638..000000000 --- a/packages/pg/lib/crypto/utils-legacy.js +++ /dev/null @@ -1,43 +0,0 @@ -'use strict' -// This file contains crypto utility functions for versions of Node.js < 15.0.0, -// which does not support the WebCrypto.subtle API. - -const nodeCrypto = require('crypto') - -function md5(string) { - return nodeCrypto.createHash('md5').update(string, 'utf-8').digest('hex') -} - -// See AuthenticationMD5Password at https://www.postgresql.org/docs/current/static/protocol-flow.html -function postgresMd5PasswordHash(user, password, salt) { - const inner = md5(password + user) - const outer = md5(Buffer.concat([Buffer.from(inner), salt])) - return 'md5' + outer -} - -function sha256(text) { - return nodeCrypto.createHash('sha256').update(text).digest() -} - -function hashByName(hashName, text) { - hashName = hashName.replace(/(\D)-/, '$1') // e.g. SHA-256 -> SHA256 - return nodeCrypto.createHash(hashName).update(text).digest() -} - -function hmacSha256(key, msg) { - return nodeCrypto.createHmac('sha256', key).update(msg).digest() -} - -async function deriveKey(password, salt, iterations) { - return nodeCrypto.pbkdf2Sync(password, salt, iterations, 32, 'sha256') -} - -module.exports = { - postgresMd5PasswordHash, - randomBytes: nodeCrypto.randomBytes, - deriveKey, - sha256, - hashByName, - hmacSha256, - md5, -} diff --git a/packages/pg/lib/crypto/utils-webcrypto.js b/packages/pg/lib/crypto/utils-webcrypto.js deleted file mode 100644 index 65aa4a182..000000000 --- a/packages/pg/lib/crypto/utils-webcrypto.js +++ /dev/null @@ -1,89 +0,0 @@ -const nodeCrypto = require('crypto') - -module.exports = { - postgresMd5PasswordHash, - randomBytes, - deriveKey, - sha256, - hashByName, - hmacSha256, - md5, -} - -/** - * The Web Crypto API - grabbed from the Node.js library or the global - * @type Crypto - */ -// eslint-disable-next-line no-undef -const webCrypto = nodeCrypto.webcrypto || globalThis.crypto -/** - * The SubtleCrypto API for low level crypto operations. - * @type SubtleCrypto - */ -const subtleCrypto = webCrypto.subtle -const textEncoder = new TextEncoder() - -/** - * - * @param {*} length - * @returns - */ -function randomBytes(length) { - return webCrypto.getRandomValues(Buffer.alloc(length)) -} - -async function md5(string) { - try { - return nodeCrypto.createHash('md5').update(string, 'utf-8').digest('hex') - } catch (e) { - // `createHash()` failed so we are probably not in Node.js, use the WebCrypto API instead. - // Note that the MD5 algorithm on WebCrypto is not available in Node.js. - // This is why we cannot just use WebCrypto in all environments. - const data = typeof string === 'string' ? textEncoder.encode(string) : string - const hash = await subtleCrypto.digest('MD5', data) - return Array.from(new Uint8Array(hash)) - .map((b) => b.toString(16).padStart(2, '0')) - .join('') - } -} - -// See AuthenticationMD5Password at https://www.postgresql.org/docs/current/static/protocol-flow.html -async function postgresMd5PasswordHash(user, password, salt) { - const inner = await md5(password + user) - const outer = await md5(Buffer.concat([Buffer.from(inner), salt])) - return 'md5' + outer -} - -/** - * Create a SHA-256 digest of the given data - * @param {Buffer} data - */ -async function sha256(text) { - return await subtleCrypto.digest('SHA-256', text) -} - -async function hashByName(hashName, text) { - return await subtleCrypto.digest(hashName, text) -} - -/** - * Sign the message with the given key - * @param {ArrayBuffer} keyBuffer - * @param {string} msg - */ -async function hmacSha256(keyBuffer, msg) { - const key = await subtleCrypto.importKey('raw', keyBuffer, { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']) - return await subtleCrypto.sign('HMAC', key, textEncoder.encode(msg)) -} - -/** - * Derive a key from the password and salt - * @param {string} password - * @param {Uint8Array} salt - * @param {number} iterations - */ -async function deriveKey(password, salt, iterations) { - const key = await subtleCrypto.importKey('raw', textEncoder.encode(password), 'PBKDF2', false, ['deriveBits']) - const params = { name: 'PBKDF2', hash: 'SHA-256', salt: salt, iterations: iterations } - return await subtleCrypto.deriveBits(params, key, 32 * 8, ['deriveBits']) -} diff --git a/packages/pg/lib/crypto/utils.js b/packages/pg/lib/crypto/utils.js index 9644b150f..65aa4a182 100644 --- a/packages/pg/lib/crypto/utils.js +++ b/packages/pg/lib/crypto/utils.js @@ -1,9 +1,89 @@ -'use strict' - -const useLegacyCrypto = parseInt(process.versions && process.versions.node && process.versions.node.split('.')[0]) < 15 -if (useLegacyCrypto) { - // We are on an old version of Node.js that requires legacy crypto utilities. - module.exports = require('./utils-legacy') -} else { - module.exports = require('./utils-webcrypto') +const nodeCrypto = require('crypto') + +module.exports = { + postgresMd5PasswordHash, + randomBytes, + deriveKey, + sha256, + hashByName, + hmacSha256, + md5, +} + +/** + * The Web Crypto API - grabbed from the Node.js library or the global + * @type Crypto + */ +// eslint-disable-next-line no-undef +const webCrypto = nodeCrypto.webcrypto || globalThis.crypto +/** + * The SubtleCrypto API for low level crypto operations. + * @type SubtleCrypto + */ +const subtleCrypto = webCrypto.subtle +const textEncoder = new TextEncoder() + +/** + * + * @param {*} length + * @returns + */ +function randomBytes(length) { + return webCrypto.getRandomValues(Buffer.alloc(length)) +} + +async function md5(string) { + try { + return nodeCrypto.createHash('md5').update(string, 'utf-8').digest('hex') + } catch (e) { + // `createHash()` failed so we are probably not in Node.js, use the WebCrypto API instead. + // Note that the MD5 algorithm on WebCrypto is not available in Node.js. + // This is why we cannot just use WebCrypto in all environments. + const data = typeof string === 'string' ? textEncoder.encode(string) : string + const hash = await subtleCrypto.digest('MD5', data) + return Array.from(new Uint8Array(hash)) + .map((b) => b.toString(16).padStart(2, '0')) + .join('') + } +} + +// See AuthenticationMD5Password at https://www.postgresql.org/docs/current/static/protocol-flow.html +async function postgresMd5PasswordHash(user, password, salt) { + const inner = await md5(password + user) + const outer = await md5(Buffer.concat([Buffer.from(inner), salt])) + return 'md5' + outer +} + +/** + * Create a SHA-256 digest of the given data + * @param {Buffer} data + */ +async function sha256(text) { + return await subtleCrypto.digest('SHA-256', text) +} + +async function hashByName(hashName, text) { + return await subtleCrypto.digest(hashName, text) +} + +/** + * Sign the message with the given key + * @param {ArrayBuffer} keyBuffer + * @param {string} msg + */ +async function hmacSha256(keyBuffer, msg) { + const key = await subtleCrypto.importKey('raw', keyBuffer, { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']) + return await subtleCrypto.sign('HMAC', key, textEncoder.encode(msg)) +} + +/** + * Derive a key from the password and salt + * @param {string} password + * @param {Uint8Array} salt + * @param {number} iterations + */ +async function deriveKey(password, salt, iterations) { + const key = await subtleCrypto.importKey('raw', textEncoder.encode(password), 'PBKDF2', false, ['deriveBits']) + const params = { name: 'PBKDF2', hash: 'SHA-256', salt: salt, iterations: iterations } + return await subtleCrypto.deriveBits(params, key, 32 * 8, ['deriveBits']) } diff --git a/packages/pg/lib/utils.js b/packages/pg/lib/utils.js index 1bbdebaf9..638b43970 100644 --- a/packages/pg/lib/utils.js +++ b/packages/pg/lib/utils.js @@ -2,8 +2,7 @@ const defaults = require('./defaults') -const util = require('util') -const { isDate } = util.types || util // Node 8 doesn't have `util.types` +const { isDate } = require('util/types') function escapeElement(elementRepresentation) { const escaped = elementRepresentation.replace(/\\/g, '\\\\').replace(/"/g, '\\"') @@ -11,12 +10,6 @@ function escapeElement(elementRepresentation) { return '"' + escaped + '"' } -// Node.js v4 does not support those Buffer.from params -const bufferFrom = - Buffer.from(new Uint8Array(1).buffer, 0, 0).length === 0 - ? Buffer.from - : (arrayBuffer, byteOffset, length) => Buffer.from(arrayBuffer).slice(byteOffset, byteOffset + length) - // convert a JS array to a postgres array literal // uses comma separator so won't work for types like box that use // a different array separator. @@ -33,7 +26,7 @@ function arrayString(val) { result += arrayString(item) } else if (ArrayBuffer.isView(item)) { if (!(item instanceof Buffer)) { - item = bufferFrom(item.buffer, item.byteOffset, item.byteLength) + item = Buffer.from(item.buffer, item.byteOffset, item.byteLength) } result += '\\\\x' + item.toString('hex') } else { @@ -58,7 +51,7 @@ const prepareValue = function (val, seen) { return val } if (ArrayBuffer.isView(val)) { - return bufferFrom(val.buffer, val.byteOffset, val.byteLength) + return Buffer.from(val.buffer, val.byteOffset, val.byteLength) } if (isDate(val)) { if (defaults.parseInputDatesAsUTC) { diff --git a/packages/pg/test/integration/client/async-stack-trace-tests.js b/packages/pg/test/integration/client/async-stack-trace-tests.js index 8f289f5ad..567851a72 100644 --- a/packages/pg/test/integration/client/async-stack-trace-tests.js +++ b/packages/pg/test/integration/client/async-stack-trace-tests.js @@ -2,50 +2,41 @@ const helper = require('../test-helper') const pg = helper.pg -process.on('unhandledRejection', function (e) { - console.error(e, e.stack) - process.exit(1) -}) - const suite = new helper.Suite() -// these tests will only work for if --async-stack-traces is on, which is the default starting in node 16. -const NODE_MAJOR_VERSION = +process.versions.node.split('.')[0] -if (NODE_MAJOR_VERSION >= 16) { - suite.test('promise API async stack trace in pool', async function outerFunction() { - async function innerFunction() { - const pool = new pg.Pool() - await pool.query('SELECT test from nonexistent') - } - try { - await innerFunction() - throw Error('should have errored') - } catch (e) { - const stack = e.stack - if (!e.stack.includes('innerFunction') || !e.stack.includes('outerFunction')) { - throw Error('async stack trace does not contain wanted values: ' + stack, { cause: e }) - } +suite.test('promise API async stack trace in pool', async function outerFunction() { + async function innerFunction() { + const pool = new pg.Pool() + await pool.query('SELECT test from nonexistent') + } + try { + await innerFunction() + throw Error('should have errored') + } catch (e) { + const stack = e.stack + if (!e.stack.includes('innerFunction') || !e.stack.includes('outerFunction')) { + throw Error('async stack trace does not contain wanted values: ' + stack, { cause: e }) } - }) + } +}) - suite.test('promise API async stack trace in client', async function outerFunction() { - async function innerFunction() { - const client = new pg.Client() - await client.connect() - try { - await client.query('SELECT test from nonexistent') - } finally { - client.end() - } - } +suite.test('promise API async stack trace in client', async function outerFunction() { + async function innerFunction() { + const client = new pg.Client() + await client.connect() try { - await innerFunction() - throw Error('should have errored') - } catch (e) { - const stack = e.stack - if (!e.stack.includes('innerFunction') || !e.stack.includes('outerFunction')) { - throw Error('async stack trace does not contain wanted values: ' + stack, { cause: e }) - } + await client.query('SELECT test from nonexistent') + } finally { + client.end() } - }) -} + } + try { + await innerFunction() + throw Error('should have errored') + } catch (e) { + const stack = e.stack + if (!e.stack.includes('innerFunction') || !e.stack.includes('outerFunction')) { + throw Error('async stack trace does not contain wanted values: ' + stack, { cause: e }) + } + } +}) diff --git a/packages/pg/test/integration/client/query-as-promise-tests.js b/packages/pg/test/integration/client/query-as-promise-tests.js index 8e1ba5c71..8c0fcae72 100644 --- a/packages/pg/test/integration/client/query-as-promise-tests.js +++ b/packages/pg/test/integration/client/query-as-promise-tests.js @@ -4,11 +4,6 @@ const helper = require('../test-helper') const pg = helper.pg const assert = require('assert') -process.on('unhandledRejection', function (e) { - console.error(e, e.stack) - process.exit(1) -}) - const suite = new helper.Suite() suite.test('promise API', (cb) => {