client_handshake.js 6.3 KB

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