UNPKG

mariadb

Version:
199 lines (171 loc) 7.05 kB
// SPDX-License-Identifier: LGPL-2.1-or-later // Copyright (c) 2015-2024 MariaDB Corporation Ab const PluginAuth = require('./plugin-auth'); const InitialHandshake = require('./initial-handshake'); const ClientCapabilities = require('../client-capabilities'); const Capabilities = require('../../../const/capabilities'); const SslRequest = require('../ssl-request'); const Errors = require('../../../misc/errors'); const NativePasswordAuth = require('./native-password-auth'); const os = require('os'); const Iconv = require('iconv-lite'); const Crypto = require('crypto'); const driverVersion = require('../../../../package.json').version; /** * Handshake response */ class Handshake extends PluginAuth { constructor(auth, getSocket, multiAuthResolver, reject) { super(null, multiAuthResolver, reject); this.sequenceNo = 0; this.compressSequenceNo = 0; this.auth = auth; this.getSocket = getSocket; this.counter = 0; this.onPacketReceive = this.parseHandshakeInit; } start(out, opts, info) {} parseHandshakeInit(packet, out, opts, info) { if (packet.peek() === 0xff) { //in case that some host is not permit to connect server const authErr = packet.readError(info); authErr.fatal = true; return this.throwError(authErr, info); } let handshake = new InitialHandshake(packet, info); ClientCapabilities.init(opts, info); this.pluginName = handshake.pluginName; if (opts.ssl) { if (info.serverCapabilities & Capabilities.SSL) { info.clientCapabilities |= Capabilities.SSL; SslRequest.send(this, out, info, opts); this.auth._createSecureContext(info, () => { // mark self-signed error only if was not explicitly forced const secureSocket = this.getSocket(); info.selfSignedCertificate = !secureSocket.authorized; info.tlsAuthorizationError = secureSocket.authorizationError; const serverCert = secureSocket.getPeerCertificate(false); info.tlsCert = serverCert; info.tlsFingerprint = serverCert ? serverCert.fingerprint256.replace(/:/gi, '').toLowerCase() : null; Handshake.send.call(this, this, out, opts, handshake.pluginName, info); }); } else { return this.throwNewError( 'Trying to connect with ssl, but ssl not enabled in the server', true, info, '08S01', Errors.ER_SERVER_SSL_DISABLED ); } } else { Handshake.send(this, out, opts, handshake.pluginName, info); } this.onPacketReceive = this.auth.handshakeResult.bind(this.auth); } permitHash() { return this.pluginName !== 'mysql_clear_password'; } hash(conf) { // mysql_native_password hash let hash = Crypto.createHash('sha1'); let stage1 = hash.update(conf.password, 'utf8').digest(); hash = Crypto.createHash('sha1'); return hash.update(stage1).digest(); } /** * Send Handshake response packet * see https://mariadb.com/kb/en/library/1-connecting-connecting/#handshake-response-packet * * @param cmd current handshake command * @param out output writer * @param opts connection options * @param pluginName plugin name * @param info connection information */ static send(cmd, out, opts, pluginName, info) { out.startPacket(cmd); info.defaultPluginName = pluginName; const pwd = Array.isArray(opts.password) ? opts.password[0] : opts.password; let authToken; let authPlugin; switch (pluginName) { case 'mysql_clear_password': authToken = Buffer.from(pwd); authPlugin = 'mysql_clear_password'; break; default: authToken = NativePasswordAuth.encryptSha1Password(pwd, info.seed); authPlugin = 'mysql_native_password'; break; } out.writeInt32(Number(info.clientCapabilities & BigInt(0xffffffff))); out.writeInt32(1024 * 1024 * 1024); // max packet size // if collation and id < 255, set it directly // is not, additional command SET NAMES xx [COLLATE yy] will be issued out.writeInt8(opts.collation && opts.collation.index <= 255 ? opts.collation.index : 224); for (let i = 0; i < 19; i++) { out.writeInt8(0); } out.writeInt32(Number(info.clientCapabilities >> 32n)); //null encoded user out.writeString(opts.user || ''); out.writeInt8(0); if (info.serverCapabilities & Capabilities.PLUGIN_AUTH_LENENC_CLIENT_DATA) { out.writeLengthCoded(authToken.length); out.writeBuffer(authToken, 0, authToken.length); } else if (info.serverCapabilities & Capabilities.SECURE_CONNECTION) { out.writeInt8(authToken.length); out.writeBuffer(authToken, 0, authToken.length); } else { out.writeBuffer(authToken, 0, authToken.length); out.writeInt8(0); } if (info.clientCapabilities & Capabilities.CONNECT_WITH_DB) { out.writeString(opts.database); out.writeInt8(0); info.database = opts.database; } if (info.clientCapabilities & Capabilities.PLUGIN_AUTH) { out.writeString(authPlugin); out.writeInt8(0); } if (info.clientCapabilities & Capabilities.CONNECT_ATTRS) { out.writeInt8(0xfc); let initPos = out.pos; //save position, assuming connection attributes length will be less than 2 bytes length out.writeInt16(0); const encoding = info.collation ? info.collation.charset : 'utf8'; Handshake.writeAttribute(out, '_client_name', encoding); Handshake.writeAttribute(out, 'MariaDB connector/Node', encoding); Handshake.writeAttribute(out, '_client_version', encoding); Handshake.writeAttribute(out, driverVersion, encoding); const address = cmd.getSocket().address().address; if (address) { Handshake.writeAttribute(out, '_server_host', encoding); Handshake.writeAttribute(out, address, encoding); } Handshake.writeAttribute(out, '_os', encoding); Handshake.writeAttribute(out, process.platform, encoding); Handshake.writeAttribute(out, '_client_host', encoding); Handshake.writeAttribute(out, os.hostname(), encoding); Handshake.writeAttribute(out, '_node_version', encoding); Handshake.writeAttribute(out, process.versions.node, encoding); if (opts.connectAttributes !== true) { let attrNames = Object.keys(opts.connectAttributes); for (let k = 0; k < attrNames.length; ++k) { Handshake.writeAttribute(out, attrNames[k], encoding); Handshake.writeAttribute(out, opts.connectAttributes[attrNames[k]], encoding); } } //write end size out.writeInt16AtPos(initPos); } out.flushPacket(); } static writeAttribute(out, val, encoding) { let param = Buffer.isEncoding(encoding) ? Buffer.from(val, encoding) : Iconv.encode(val, encoding); out.writeLengthCoded(param.length); out.writeBuffer(param, 0, param.length); } } module.exports = Handshake;