Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adapt to Cloudflare Workers environment #2289

Open
wants to merge 18 commits into
base: master
Choose a base branch
from
Open
38 changes: 12 additions & 26 deletions lib/auth_41.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,21 +24,7 @@
server stores sha1(sha1(password)) ( hash_stag2)
*/

const crypto = require('crypto');

function sha1(msg, msg1, msg2) {
const hash = crypto.createHash('sha1');
hash.update(msg);
if (msg1) {
hash.update(msg1);
}

if (msg2) {
hash.update(msg2);
}

return hash.digest();
}
const crypto = require('./utils/crypto');

function xor(a, b) {
const result = Buffer.allocUnsafe(a.length);
Expand All @@ -50,37 +36,37 @@ function xor(a, b) {

exports.xor = xor;

function token(password, scramble1, scramble2) {
async function token(password, scramble1, scramble2) {
if (!password) {
return Buffer.alloc(0);
}
const stage1 = sha1(password);
return exports.calculateTokenFromPasswordSha(stage1, scramble1, scramble2);
const stage1 = await crypto.sha1(password);
return await exports.calculateTokenFromPasswordSha(stage1, scramble1, scramble2);
}

exports.calculateTokenFromPasswordSha = function(
exports.calculateTokenFromPasswordSha = async function(
passwordSha,
scramble1,
scramble2
) {
// we use AUTH 41 here, and we need only the bytes we just need.
const authPluginData1 = scramble1.slice(0, 8);
const authPluginData2 = scramble2.slice(0, 12);
const stage2 = sha1(passwordSha);
const stage3 = sha1(authPluginData1, authPluginData2, stage2);
const stage2 = await crypto.sha1(passwordSha);
const stage3 = await crypto.sha1(authPluginData1, authPluginData2, stage2);
return xor(stage3, passwordSha);
};

exports.calculateToken = token;

exports.verifyToken = function(publicSeed1, publicSeed2, token, doubleSha) {
const hashStage1 = xor(token, sha1(publicSeed1, publicSeed2, doubleSha));
const candidateHash2 = sha1(hashStage1);
exports.verifyToken = async function(publicSeed1, publicSeed2, token, doubleSha) {
const hashStage1 = xor(token, await crypto.sha1(publicSeed1, publicSeed2, doubleSha));
const candidateHash2 = await crypto.sha1(hashStage1);
return candidateHash2.compare(doubleSha) === 0;
};

exports.doubleSha1 = function(password) {
return sha1(sha1(password));
exports.doubleSha1 = async function(password) {
return await crypto.sha1(await crypto.sha1(password));
};

function xorRotating(a, seed) {
Expand Down
32 changes: 13 additions & 19 deletions lib/auth_plugins/caching_sha2_password.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// https://mysqlserverteam.com/mysql-8-0-4-new-default-authentication-plugin-caching_sha2_password/

const PLUGIN_NAME = 'caching_sha2_password';
const crypto = require('crypto');
const crypto = require('../utils/crypto');
const { xor, xorRotating } = require('../auth_41');

const REQUEST_SERVER_KEY_PACKET = Buffer.from([2]);
Expand All @@ -15,28 +15,22 @@ const STATE_TOKEN_SENT = 1;
const STATE_WAIT_SERVER_KEY = 2;
const STATE_FINAL = -1;

function sha256(msg) {
const hash = crypto.createHash('sha256');
hash.update(msg);
return hash.digest();
}

function calculateToken(password, scramble) {
async function calculateToken(password, scramble) {
if (!password) {
return Buffer.alloc(0);
}
const stage1 = sha256(Buffer.from(password));
const stage2 = sha256(stage1);
const stage3 = sha256(Buffer.concat([stage2, scramble]));
const stage1 = await crypto.sha256(Buffer.from(password));
const stage2 = await crypto.sha256(stage1);
const stage3 = await crypto.sha256(Buffer.concat([stage2, scramble]));
return xor(stage1, stage3);
}

function encrypt(password, scramble, key) {
async function encrypt(password, scramble, key) {
const stage1 = xorRotating(
Buffer.from(`${password}\0`, 'utf8'),
scramble
);
return crypto.publicEncrypt(key, stage1);
return await crypto.publicEncrypt(key, stage1);
}

module.exports = (pluginOptions = {}) => ({ connection }) => {
Expand All @@ -45,18 +39,18 @@ module.exports = (pluginOptions = {}) => ({ connection }) => {

const password = connection.config.password;

const authWithKey = serverKey => {
const _password = encrypt(password, scramble, serverKey);
const authWithKey = async serverKey => {
const _password = await encrypt(password, scramble, serverKey);
state = STATE_FINAL;
return _password;
};

return data => {
return async data => {
switch (state) {
case STATE_INITIAL:
scramble = data.slice(0, 20);
state = STATE_TOKEN_SENT;
return calculateToken(password, scramble);
return await calculateToken(password, scramble);

case STATE_TOKEN_SENT:
if (FAST_AUTH_SUCCESS_PACKET.equals(data)) {
Expand All @@ -76,7 +70,7 @@ module.exports = (pluginOptions = {}) => ({ connection }) => {

// if client provides key we can save one extra roundrip on first connection
if (pluginOptions.serverPublicKey) {
return authWithKey(pluginOptions.serverPublicKey);
return await authWithKey(pluginOptions.serverPublicKey);
}

state = STATE_WAIT_SERVER_KEY;
Expand All @@ -89,7 +83,7 @@ module.exports = (pluginOptions = {}) => ({ connection }) => {
if (pluginOptions.onServerPublicKey) {
pluginOptions.onServerPublicKey(data);
}
return authWithKey(data);
return await authWithKey(data);
case STATE_FINAL:
throw new Error(
`Unexpected data in AuthMoreData packet received by ${PLUGIN_NAME} plugin in STATE_FINAL state.`
Expand Down
6 changes: 3 additions & 3 deletions lib/auth_plugins/mysql_native_password.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,18 @@ module.exports = pluginOptions => ({ connection, command }) => {
command.passwordSha1 ||
pluginOptions.passwordSha1 ||
connection.config.passwordSha1;
return data => {
return async data => {
const authPluginData1 = data.slice(0, 8);
const authPluginData2 = data.slice(8, 20);
let authToken;
if (passwordSha1) {
authToken = auth41.calculateTokenFromPasswordSha(
authToken = await auth41.calculateTokenFromPasswordSha(
passwordSha1,
authPluginData1,
authPluginData2
);
} else {
authToken = auth41.calculateToken(
authToken = await auth41.calculateToken(
password,
authPluginData1,
authPluginData2
Expand Down
16 changes: 8 additions & 8 deletions lib/auth_plugins/sha256_password.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
'use strict';

const PLUGIN_NAME = 'sha256_password';
const crypto = require('crypto');
const crypto = require('../utils/crypto');
const { xorRotating } = require('../auth_41');

const REQUEST_SERVER_KEY_PACKET = Buffer.from([1]);
Expand All @@ -10,12 +10,12 @@ const STATE_INITIAL = 0;
const STATE_WAIT_SERVER_KEY = 1;
const STATE_FINAL = -1;

function encrypt(password, scramble, key) {
async function encrypt(password, scramble, key) {
const stage1 = xorRotating(
Buffer.from(`${password}\0`, 'utf8'),
scramble
);
return crypto.publicEncrypt(key, stage1);
return await crypto.publicEncrypt(key, stage1);
}

module.exports = (pluginOptions = {}) => ({ connection }) => {
Expand All @@ -24,19 +24,19 @@ module.exports = (pluginOptions = {}) => ({ connection }) => {

const password = connection.config.password;

const authWithKey = serverKey => {
const _password = encrypt(password, scramble, serverKey);
const authWithKey = async serverKey => {
const _password = await encrypt(password, scramble, serverKey);
state = STATE_FINAL;
return _password;
};

return data => {
return async data => {
switch (state) {
case STATE_INITIAL:
scramble = data.slice(0, 20);
// if client provides key we can save one extra roundrip on first connection
if (pluginOptions.serverPublicKey) {
return authWithKey(pluginOptions.serverPublicKey);
return await authWithKey(pluginOptions.serverPublicKey);
}

state = STATE_WAIT_SERVER_KEY;
Expand All @@ -46,7 +46,7 @@ module.exports = (pluginOptions = {}) => ({ connection }) => {
if (pluginOptions.onServerPublicKey) {
pluginOptions.onServerPublicKey(data);
}
return authWithKey(data);
return await authWithKey(data);
case STATE_FINAL:
throw new Error(
`Unexpected data in AuthMoreData packet received by ${PLUGIN_NAME} plugin in STATE_FINAL state.`
Expand Down
6 changes: 5 additions & 1 deletion lib/commands/change_user.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,11 @@ class ChangeUser extends Command {
connection.clientEncoding = CharsetToEncoding[this.charsetNumber];
// clear prepared statements cache as all statements become invalid after changeUser
connection._statements.clear();
connection.writePacket(newPacket.toPacket());
newPacket.toPacket().then(packet => {
connection.writePacket(packet);
}).catch(err => {
this.onResult(err);
});
// check if the server supports multi-factor authentication
const multiFactorAuthentication = connection.serverCapabilityFlags & ClientConstants.MULTI_FACTOR_AUTHENTICATION;
if (multiFactorAuthentication) {
Expand Down
29 changes: 10 additions & 19 deletions lib/commands/client_handshake.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const Packets = require('../packets/index.js');
const ClientConstants = require('../constants/client.js');
const CharsetToEncoding = require('../constants/charset_encodings.js');
const auth41 = require('../auth_41.js');
const {secureStream} = require('../stream.js');

function flagNames(flags) {
const res = [];
Expand Down Expand Up @@ -46,7 +47,7 @@ class ClientHandshake extends Command {
connection.writePacket(sslRequest.toPacket());
}

sendCredentials(connection) {
async sendCredentials(connection) {
if (connection.config.debug) {
// eslint-disable-next-line
console.log(
Expand Down Expand Up @@ -80,22 +81,22 @@ class ClientHandshake extends Command {
compress: connection.config.compress,
connectAttributes: connection.config.connectAttributes
});
connection.writePacket(handshakeResponse.toPacket());
connection.writePacket(await handshakeResponse.toPacket());
}

calculateNativePasswordAuthToken(authPluginData) {
async calculateNativePasswordAuthToken(authPluginData) {
// TODO: dont split into authPluginData1 and authPluginData2, instead join when 1 & 2 received
const authPluginData1 = authPluginData.slice(0, 8);
const authPluginData2 = authPluginData.slice(8, 20);
let authToken;
if (this.passwordSha1) {
authToken = auth41.calculateTokenFromPasswordSha(
authToken = await auth41.calculateTokenFromPasswordSha(
this.passwordSha1,
authPluginData1,
authPluginData2
);
} else {
authToken = auth41.calculateToken(
authToken = await auth41.calculateToken(
this.password,
authPluginData1,
authPluginData2
Expand Down Expand Up @@ -146,21 +147,11 @@ class ClientHandshake extends Command {
// send ssl upgrade request and immediately upgrade connection to secure
this.clientFlags |= ClientConstants.SSL;
this.sendSSLRequest(connection);
connection.startTLS(err => {
// after connection is secure
if (err) {
// SSL negotiation error are fatal
err.code = 'HANDSHAKE_SSL_ERROR';
err.fatal = true;
this.emit('error', err);
return;
}
// rest of communication is encrypted
this.sendCredentials(connection);
});
} else {
this.sendCredentials(connection);
secureStream(connection)
}
this.sendCredentials(connection).catch(err => {
this.emit('error', err);
});
if (multiFactorAuthentication) {
// if the server supports multi-factor authentication, we enable it in
// the client
Expand Down
7 changes: 3 additions & 4 deletions lib/commands/command.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
'use strict';

const EventEmitter = require('events').EventEmitter;
const Timers = require('timers');

class Command extends EventEmitter {
constructor() {
Expand Down Expand Up @@ -29,7 +28,7 @@ class Command extends EventEmitter {
const err = packet.asError(connection.clientEncoding);
err.sql = this.sql || this.query;
if (this.queryTimeout) {
Timers.clearTimeout(this.queryTimeout);
clearTimeout(this.queryTimeout);
this.queryTimeout = null;
}
if (this.onResult) {
Expand All @@ -45,10 +44,10 @@ class Command extends EventEmitter {
this.next = this.next(packet, connection);
if (this.next) {
return false;
}
}
this.emit('end');
return true;

}
}

Expand Down
Loading
Loading