diff --git a/lib/auth_41.js b/lib/auth_41.js index 15d6d032eb..87cc891006 100644 --- a/lib/auth_41.js +++ b/lib/auth_41.js @@ -39,6 +39,7 @@ function sha1(msg, msg1, msg2) { return hash.digest(); } +exports.sha1 = sha1; function xor(a, b) { const result = Buffer.allocUnsafe(a.length); diff --git a/lib/auth_plugins/index.js b/lib/auth_plugins/index.js index dd4dc98665..866a7daceb 100644 --- a/lib/auth_plugins/index.js +++ b/lib/auth_plugins/index.js @@ -1,8 +1,16 @@ 'use strict'; module.exports = { - caching_sha2_password: require('./caching_sha2_password'), - mysql_clear_password: require('./mysql_clear_password'), - mysql_native_password: require('./mysql_native_password'), - sha256_password: require('./sha256_password'), + get caching_sha2_password() { + return require('./caching_sha2_password'); + }, + get mysql_clear_password() { + return require('./mysql_clear_password'); + }, + get mysql_native_password() { + return require('./mysql_native_password'); + }, + get sha256_password() { + return require('./sha256_password'); + } }; diff --git a/lib/commands/client_handshake.js b/lib/commands/client_handshake.js index c8fb1e1eaa..29e6777cf3 100644 --- a/lib/commands/client_handshake.js +++ b/lib/commands/client_handshake.js @@ -15,6 +15,9 @@ 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 auth323 = require('../auth_323.js'); +const caching_sha2_password = require('../auth_plugins/caching_sha2_password.js'); +const native_password = require('../auth_plugins/mysql_native_password.js'); function flagNames(flags) { const res = []; @@ -67,17 +70,35 @@ class ClientHandshake extends Command { this.password3 = connection.config.password3; this.passwordSha1 = connection.config.passwordSha1; this.database = connection.config.database; - this.autPluginName = this.handshake.autPluginName; + + let authToken; + if (this.handshake.capabilityFlags & ClientConstants.PLUGIN_AUTH) { + this.authPluginName = this.handshake.authPluginName; + const pluginData = Buffer.concat([this.handshake.authPluginData1, this.handshake.authPluginData2]); + const plugin = connection.getConnectAuthPlugin(this.authPluginName); + const pluginInstance = plugin({ connection, command: this }); + authToken = pluginInstance(pluginData); + connection._authPlugin = pluginInstance; + } else { + if (this.handshake.capabilityFlags & ClientConstants.SECURE_CONNECTION) { + authToken = this.calculateNativePasswordAuthToken( + this.handshake.authPluginData1 + ); + } else { + authToken = auth323.calculateToken( + this.password, + this.handshake.authPluginData1 + ); + } + } + const handshakeResponse = new Packets.HandshakeResponse({ flags: this.clientFlags, user: this.user, database: this.database, - password: this.password, - passwordSha1: this.passwordSha1, charsetNumber: connection.config.charsetNumber, - authPluginData1: this.handshake.authPluginData1, - authPluginData2: this.handshake.authPluginData2, - compress: connection.config.compress, + authPluginName: this.authPluginName, + authPluginData: authToken, connectAttributes: connection.config.connectAttributes }); connection.writePacket(handshakeResponse.toPacket()); @@ -230,6 +251,7 @@ class ClientHandshake extends Command { enableCompression(connection); } } + connection._authPlugin = null; if (this.onResult) { this.onResult(null); } diff --git a/lib/connection.js b/lib/connection.js index d6ad7a4f9b..c9b3ae02cc 100644 --- a/lib/connection.js +++ b/lib/connection.js @@ -897,6 +897,26 @@ class Connection extends EventEmitter { return quitCmd; } + // given server hello packet, connection config and standard supported plugins + // return the plugin to use for authentication + getConnectAuthPlugin(pluginName) { + // note that defaultAuthenticationPlugin semantics is "override the one sent by the server and use this instead for initial connection" + // if defaultAuthenticationPlugin is not set, then use passed fallback plugin name, and if that is not set then use mysql_native_password + const connectAuthPlugin = this.config.defaultAuthenticationPlugin ?? (pluginName ?? 'mysql_native_password'); + const userPlugins = this.config.authPlugins; + if (userPlugins && userPlugins[connectAuthPlugin]) { + return userPlugins[connectAuthPlugin]; + } + + // auth_plugins exports a map of plugin names to plugin "factory". We call it with default options. + // if the user needs non-default options, they can use the authPlugins option to pass in a map of plugin names to an instance of the plugin + const standardPlugins = require('./auth_plugins'); + if (standardPlugins[pluginName]) { + return standardPlugins[pluginName]({}); + } + return null; + } + static createQuery(sql, values, cb, config) { let options = { rowsAsArray: config.rowsAsArray diff --git a/lib/connection_config.js b/lib/connection_config.js index 236cf6747f..10d10ba17e 100644 --- a/lib/connection_config.js +++ b/lib/connection_config.js @@ -64,7 +64,8 @@ const validOptions = { idleTimeout: 1, Promise: 1, queueLimit: 1, - waitForConnections: 1 + waitForConnections: 1, + defaultAuthenticationPlugin: 1 }; class ConnectionConfig { @@ -165,6 +166,7 @@ class ConnectionConfig { : options.charsetNumber || Charsets.UTF8MB4_UNICODE_CI; this.compress = options.compress || false; this.authPlugins = options.authPlugins; + this.defaultAuthenticationPlugin = options.defaultAuthenticationPlugin; this.authSwitchHandler = options.authSwitchHandler; this.clientFlags = ConnectionConfig.mergeFlags( ConnectionConfig.getDefaultFlags(options), diff --git a/lib/packets/handshake.js b/lib/packets/handshake.js index 239387ca98..5e31c304d3 100644 --- a/lib/packets/handshake.js +++ b/lib/packets/handshake.js @@ -3,7 +3,7 @@ const Packet = require('../packets/packet'); const ClientConstants = require('../constants/client.js'); -// https://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::Handshake +// https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_connection_phase_packets_protocol_handshake.html class Handshake { constructor(args) { @@ -15,7 +15,7 @@ class Handshake { this.authPluginData2 = args.authPluginData2; this.characterSet = args.characterSet; this.statusFlags = args.statusFlags; - this.autPluginName = args.autPluginName; + this.authPluginName = args.authPluginName || 'mysql_native_password'; } setScrambleData(cb) { @@ -51,7 +51,7 @@ class Handshake { packet.skip(10); packet.writeBuffer(this.authPluginData2); packet.writeInt8(0); - packet.writeString('mysql_native_password', 'latin1'); + packet.writeString(this.authPluginName, 'latin1'); packet.writeInt8(0); return packet; } @@ -102,7 +102,7 @@ class Handshake { } if (args.capabilityFlags & ClientConstants.PLUGIN_AUTH) { - args.autPluginName = packet.readNullTerminatedString('ascii'); + args.authPluginName = packet.readNullTerminatedString('ascii'); } return new Handshake(args); diff --git a/lib/packets/handshake_response.js b/lib/packets/handshake_response.js index b2dee38c94..d11d95bfda 100644 --- a/lib/packets/handshake_response.js +++ b/lib/packets/handshake_response.js @@ -4,8 +4,6 @@ const ClientConstants = require('../constants/client.js'); const CharsetToEncoding = require('../constants/charset_encodings.js'); const Packet = require('../packets/packet.js'); -const auth41 = require('../auth_41.js'); - class HandshakeResponse { constructor(handshake) { this.user = handshake.user || ''; @@ -14,24 +12,9 @@ class HandshakeResponse { this.passwordSha1 = handshake.passwordSha1; this.authPluginData1 = handshake.authPluginData1; this.authPluginData2 = handshake.authPluginData2; - this.compress = handshake.compress; this.clientFlags = handshake.flags; - // TODO: pre-4.1 auth support - let authToken; - if (this.passwordSha1) { - authToken = auth41.calculateTokenFromPasswordSha( - this.passwordSha1, - this.authPluginData1, - this.authPluginData2 - ); - } else { - authToken = auth41.calculateToken( - this.password, - this.authPluginData1, - this.authPluginData2 - ); - } - this.authToken = authToken; + this.pluginName = handshake.authPluginName; + this.authToken = handshake.authPluginData; this.charsetNumber = handshake.charsetNumber; this.encoding = CharsetToEncoding[handshake.charsetNumber]; this.connectAttributes = handshake.connectAttributes; @@ -62,8 +45,7 @@ class HandshakeResponse { packet.writeNullTerminatedString(this.database, encoding); } if (isSet('PLUGIN_AUTH')) { - // TODO: pass from config - packet.writeNullTerminatedString('mysql_native_password', 'latin1'); + packet.writeNullTerminatedString(this.pluginName, 'latin1'); } if (isSet('CONNECT_ATTRS')) { const connectAttributes = this.connectAttributes || {};