Skip to content

Commit

Permalink
support cacheing-sha2
Browse files Browse the repository at this point in the history
  • Loading branch information
shiyuhang0 committed Dec 25, 2023
1 parent f2fe8d9 commit 758f829
Show file tree
Hide file tree
Showing 4 changed files with 77 additions and 29 deletions.
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
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
14 changes: 13 additions & 1 deletion lib/utils/nodecrypto.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,18 @@ async function sha1(msg,msg1,msg2) {
return hash.digest();
}

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

async function publicEncrypt(key,buffer) {
return nodeCrypto.publicEncrypt(key,buffer);
}

module.exports = {
sha1
sha1,
sha256,
publicEncrypt
}
44 changes: 43 additions & 1 deletion lib/utils/webcrypto.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,48 @@ async function sha1(msg,msg1,msg2) {
return Buffer.from(arrayBuffer)
}

async function sha256(msg) {
const arrayBuffer = await subtleCrypto.digest('SHA-256', typeof msg === 'string' ? textEncoder.encode(msg) : msg)
return Buffer.from(arrayBuffer)
}

function decodeBase64(b64) {
const binString = atob(b64);
const size = binString.length;
const bytes = new Uint8Array(size);
for (let i = 0; i < size; i++) {
bytes[i] = binString.charCodeAt(i);
}
return bytes;
}

// need more test
async function publicEncrypt(key, data) {
const pemHeader = "-----BEGIN PUBLIC KEY-----\n";
const pemFooter = "\n-----END PUBLIC KEY-----";
key = key.trim();
key = key.substring(pemHeader.length, key.length - pemFooter.length);

const importedKey = await subtleCrypto.importKey(
"spki",
decodeBase64(key),
{ name: "RSA-OAEP", hash: "SHA-256" },
false,
["encrypt"],
);

const encryptedData = await subtleCrypto.encrypt(
{
name: "RSA-OAEP",
},
importedKey,
data,
);
return Buffer.from(encryptedData)
}

module.exports = {
sha1
sha1,
sha256,
publicEncrypt
}

0 comments on commit 758f829

Please sign in to comment.