client_handshake.js 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  1. 'use strict';
  2. const Command = require('./command.js');
  3. const Packets = require('../packets/index.js');
  4. const ClientConstants = require('../constants/client.js');
  5. const CharsetToEncoding = require('../constants/charset_encodings.js');
  6. const auth41 = require('../auth_41.js');
  7. function flagNames(flags) {
  8. const res = [];
  9. for (const c in ClientConstants) {
  10. if (flags & ClientConstants[c]) {
  11. res.push(c.replace(/_/g, ' ').toLowerCase());
  12. }
  13. }
  14. return res;
  15. }
  16. class ClientHandshake extends Command {
  17. constructor(clientFlags) {
  18. super();
  19. this.handshake = null;
  20. this.clientFlags = clientFlags;
  21. }
  22. start() {
  23. return ClientHandshake.prototype.handshakeInit;
  24. }
  25. sendSSLRequest(connection) {
  26. const sslRequest = new Packets.SSLRequest(
  27. this.clientFlags,
  28. connection.config.charsetNumber
  29. );
  30. connection.writePacket(sslRequest.toPacket());
  31. }
  32. sendCredentials(connection) {
  33. if (connection.config.debug) {
  34. // eslint-disable-next-line
  35. console.log(
  36. 'Sending handshake packet: flags:%d=(%s)',
  37. this.clientFlags,
  38. flagNames(this.clientFlags).join(', ')
  39. );
  40. }
  41. this.user = connection.config.user;
  42. this.password = connection.config.password;
  43. this.passwordSha1 = connection.config.passwordSha1;
  44. this.database = connection.config.database;
  45. this.autPluginName = this.handshake.autPluginName;
  46. const handshakeResponse = new Packets.HandshakeResponse({
  47. flags: this.clientFlags,
  48. user: this.user,
  49. database: this.database,
  50. password: this.password,
  51. passwordSha1: this.passwordSha1,
  52. charsetNumber: connection.config.charsetNumber,
  53. authPluginData1: this.handshake.authPluginData1,
  54. authPluginData2: this.handshake.authPluginData2,
  55. compress: connection.config.compress,
  56. connectAttributes: connection.config.connectAttributes
  57. });
  58. connection.writePacket(handshakeResponse.toPacket());
  59. }
  60. calculateNativePasswordAuthToken(authPluginData) {
  61. // TODO: dont split into authPluginData1 and authPluginData2, instead join when 1 & 2 received
  62. const authPluginData1 = authPluginData.slice(0, 8);
  63. const authPluginData2 = authPluginData.slice(8, 20);
  64. let authToken;
  65. if (this.passwordSha1) {
  66. authToken = auth41.calculateTokenFromPasswordSha(
  67. this.passwordSha1,
  68. authPluginData1,
  69. authPluginData2
  70. );
  71. } else {
  72. authToken = auth41.calculateToken(
  73. this.password,
  74. authPluginData1,
  75. authPluginData2
  76. );
  77. }
  78. return authToken;
  79. }
  80. handshakeInit(helloPacket, connection) {
  81. this.on('error', e => {
  82. connection._fatalError = e;
  83. connection._protocolError = e;
  84. });
  85. this.handshake = Packets.Handshake.fromPacket(helloPacket);
  86. if (connection.config.debug) {
  87. // eslint-disable-next-line
  88. console.log(
  89. 'Server hello packet: capability flags:%d=(%s)',
  90. this.handshake.capabilityFlags,
  91. flagNames(this.handshake.capabilityFlags).join(', ')
  92. );
  93. }
  94. connection.serverCapabilityFlags = this.handshake.capabilityFlags;
  95. connection.serverEncoding = CharsetToEncoding[this.handshake.characterSet];
  96. connection.connectionId = this.handshake.connectionId;
  97. const serverSSLSupport =
  98. this.handshake.capabilityFlags & ClientConstants.SSL;
  99. // use compression only if requested by client and supported by server
  100. connection.config.compress =
  101. connection.config.compress &&
  102. this.handshake.capabilityFlags & ClientConstants.COMPRESS;
  103. this.clientFlags = this.clientFlags | connection.config.compress;
  104. if (connection.config.ssl) {
  105. // client requires SSL but server does not support it
  106. if (!serverSSLSupport) {
  107. const err = new Error('Server does not support secure connnection');
  108. err.code = 'HANDSHAKE_NO_SSL_SUPPORT';
  109. err.fatal = true;
  110. this.emit('error', err);
  111. return false;
  112. }
  113. // send ssl upgrade request and immediately upgrade connection to secure
  114. this.clientFlags |= ClientConstants.SSL;
  115. this.sendSSLRequest(connection);
  116. connection.startTLS(err => {
  117. // after connection is secure
  118. if (err) {
  119. // SSL negotiation error are fatal
  120. err.code = 'HANDSHAKE_SSL_ERROR';
  121. err.fatal = true;
  122. this.emit('error', err);
  123. return;
  124. }
  125. // rest of communication is encrypted
  126. this.sendCredentials(connection);
  127. });
  128. } else {
  129. this.sendCredentials(connection);
  130. }
  131. return ClientHandshake.prototype.handshakeResult;
  132. }
  133. handshakeResult(packet, connection) {
  134. const marker = packet.peekByte();
  135. if (marker === 0xfe || marker === 1) {
  136. const authSwitch = require('./auth_switch');
  137. try {
  138. if (marker === 1) {
  139. authSwitch.authSwitchRequestMoreData(packet, connection, this);
  140. } else {
  141. authSwitch.authSwitchRequest(packet, connection, this);
  142. }
  143. return ClientHandshake.prototype.handshakeResult;
  144. } catch (err) {
  145. if (this.onResult) {
  146. this.onResult(err);
  147. } else {
  148. connection.emit('error', err);
  149. }
  150. return null;
  151. }
  152. }
  153. if (marker !== 0) {
  154. const err = new Error('Unexpected packet during handshake phase');
  155. if (this.onResult) {
  156. this.onResult(err);
  157. } else {
  158. connection.emit('error', err);
  159. }
  160. return null;
  161. }
  162. // this should be called from ClientHandshake command only
  163. // and skipped when called from ChangeUser command
  164. if (!connection.authorized) {
  165. connection.authorized = true;
  166. if (connection.config.compress) {
  167. const enableCompression = require('../compressed_protocol.js')
  168. .enableCompression;
  169. enableCompression(connection);
  170. }
  171. }
  172. if (this.onResult) {
  173. this.onResult(null);
  174. }
  175. return null;
  176. }
  177. }
  178. module.exports = ClientHandshake;