-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
The only place we are stuck with node's original crypto API is for generating md5 hashes, which are not supported by WebCrypto.
- Loading branch information
1 parent
2b469d0
commit 5532ca5
Showing
8 changed files
with
208 additions
and
101 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
'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) { | ||
var inner = md5(password + user) | ||
var outer = md5(Buffer.concat([Buffer.from(inner), salt])) | ||
return 'md5' + outer | ||
} | ||
|
||
function sha256(text) { | ||
return nodeCrypto.createHash('sha256').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, | ||
hmacSha256, | ||
md5, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
'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') | ||
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']) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,24 +1,26 @@ | ||
'use strict' | ||
var helper = require('./test-helper') | ||
const BufferList = require('../../buffer-list') | ||
var utils = require('../../../lib/utils') | ||
var crypto = require('../../../lib/crypto/utils') | ||
|
||
test('md5 authentication', function () { | ||
test('md5 authentication', async function () { | ||
var client = helper.createClient() | ||
client.password = '!' | ||
var salt = Buffer.from([1, 2, 3, 4]) | ||
client.connection.emit('authenticationMD5Password', { salt: salt }) | ||
await client.connection.emit('authenticationMD5Password', { salt: salt }) | ||
|
||
test('responds', function () { | ||
assert.lengthIs(client.connection.stream.packets, 1) | ||
test('should have correct encrypted data', function () { | ||
var password = utils.postgresMd5PasswordHash(client.user, client.password, salt) | ||
// how do we want to test this? | ||
assert.equalBuffers(client.connection.stream.packets[0], new BufferList().addCString(password).join(true, 'p')) | ||
setTimeout(() => | ||
test('responds', function () { | ||
assert.lengthIs(client.connection.stream.packets, 1) | ||
test('should have correct encrypted data', async function () { | ||
var password = await crypto.postgresMd5PasswordHash(client.user, client.password, salt) | ||
// how do we want to test this? | ||
assert.equalBuffers(client.connection.stream.packets[0], new BufferList().addCString(password).join(true, 'p')) | ||
}) | ||
}) | ||
}) | ||
) | ||
}) | ||
|
||
test('md5 of utf-8 strings', function () { | ||
assert.equal(utils.md5('😊'), '5deda34cd95f304948d2bc1b4a62c11e') | ||
test('md5 of utf-8 strings', async function () { | ||
assert.equal(await crypto.md5('😊'), '5deda34cd95f304948d2bc1b4a62c11e') | ||
}) |
Oops, something went wrong.