diff --git a/packages/pg/lib/crypto/utils-webcrypto.js b/packages/pg/lib/crypto/utils-webcrypto.js new file mode 100644 index 000000000..0433f010c --- /dev/null +++ b/packages/pg/lib/crypto/utils-webcrypto.js @@ -0,0 +1,83 @@ +const nodeCrypto = require('crypto') + +module.exports = { + postgresMd5PasswordHash, + randomBytes, + deriveKey, + sha256, + hmacSha256, + md5, +} + +/** + * The Web Crypto API - grabbed from the Node.js library or the global + * @type Crypto + */ +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) { + var inner = await md5(password + user) + var 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) +} + +/** + * 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 ca2b821c6..efd019b83 100644 --- a/packages/pg/lib/crypto/utils.js +++ b/packages/pg/lib/crypto/utils.js @@ -4,89 +4,6 @@ const useLegacyCrypto = parseInt(process.versions && process.versions.node && pr if (useLegacyCrypto) { // We are on an old version of Node.js that requires legacy crypto utilities. module.exports = require('./utils-legacy') - return -} - -const nodeCrypto = require('crypto') - -module.exports = { - postgresMd5PasswordHash, - randomBytes, - deriveKey, - sha256, - hmacSha256, - md5, -} - -/** - * The Web Crypto API - grabbed from the Node.js library or the global - * @type Crypto - */ -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) { - var inner = await md5(password + user) - var 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) -} - -/** - * 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']) +} else { + module.exports = require('./utils-webcrypto'); }