client_handshake.js 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  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. // This file was modified by Oracle on September 21, 2021.
  6. // Handshake workflow now supports additional authentication factors requested
  7. // by the server.
  8. // Modifications copyright (c) 2021, Oracle and/or its affiliates.
  9. 'use strict';
  10. const Command = require('./command.js');
  11. const Packets = require('../packets/index.js');
  12. const ClientConstants = require('../constants/client.js');
  13. const CharsetToEncoding = require('../constants/charset_encodings.js');
  14. const auth41 = require('../auth_41.js');
  15. function flagNames(flags) {
  16. const res = [];
  17. for (const c in ClientConstants) {
  18. if (flags & ClientConstants[c]) {
  19. res.push(c.replace(/_/g, ' ').toLowerCase());
  20. }
  21. }
  22. return res;
  23. }
  24. class ClientHandshake extends Command {
  25. constructor(clientFlags) {
  26. super();
  27. this.handshake = null;
  28. this.clientFlags = clientFlags;
  29. this.authenticationFactor = 0;
  30. }
  31. start() {
  32. return ClientHandshake.prototype.handshakeInit;
  33. }
  34. sendSSLRequest(connection) {
  35. const sslRequest = new Packets.SSLRequest(
  36. this.clientFlags,
  37. connection.config.charsetNumber
  38. );
  39. connection.writePacket(sslRequest.toPacket());
  40. }
  41. sendCredentials(connection) {
  42. if (connection.config.debug) {
  43. // eslint-disable-next-line
  44. console.log(
  45. 'Sending handshake packet: flags:%d=(%s)',
  46. this.clientFlags,
  47. flagNames(this.clientFlags).join(', ')
  48. );
  49. }
  50. this.user = connection.config.user;
  51. this.password = connection.config.password;
  52. // "password1" is an alias to the original "password" value
  53. // to make it easier to integrate multi-factor authentication
  54. this.password1 = connection.config.password;
  55. // "password2" and "password3" are the 2nd and 3rd factor authentication
  56. // passwords, which can be undefined depending on the authentication
  57. // plugin being used
  58. this.password2 = connection.config.password2;
  59. this.password3 = connection.config.password3;
  60. this.passwordSha1 = connection.config.passwordSha1;
  61. this.database = connection.config.database;
  62. this.autPluginName = this.handshake.autPluginName;
  63. const handshakeResponse = new Packets.HandshakeResponse({
  64. flags: this.clientFlags,
  65. user: this.user,
  66. database: this.database,
  67. password: this.password,
  68. passwordSha1: this.passwordSha1,
  69. charsetNumber: connection.config.charsetNumber,
  70. authPluginData1: this.handshake.authPluginData1,
  71. authPluginData2: this.handshake.authPluginData2,
  72. compress: connection.config.compress,
  73. connectAttributes: connection.config.connectAttributes
  74. });
  75. connection.writePacket(handshakeResponse.toPacket());
  76. }
  77. calculateNativePasswordAuthToken(authPluginData) {
  78. // TODO: dont split into authPluginData1 and authPluginData2, instead join when 1 & 2 received
  79. const authPluginData1 = authPluginData.slice(0, 8);
  80. const authPluginData2 = authPluginData.slice(8, 20);
  81. let authToken;
  82. if (this.passwordSha1) {
  83. authToken = auth41.calculateTokenFromPasswordSha(
  84. this.passwordSha1,
  85. authPluginData1,
  86. authPluginData2
  87. );
  88. } else {
  89. authToken = auth41.calculateToken(
  90. this.password,
  91. authPluginData1,
  92. authPluginData2
  93. );
  94. }
  95. return authToken;
  96. }
  97. handshakeInit(helloPacket, connection) {
  98. this.on('error', e => {
  99. connection._fatalError = e;
  100. connection._protocolError = e;
  101. });
  102. this.handshake = Packets.Handshake.fromPacket(helloPacket);
  103. if (connection.config.debug) {
  104. // eslint-disable-next-line
  105. console.log(
  106. 'Server hello packet: capability flags:%d=(%s)',
  107. this.handshake.capabilityFlags,
  108. flagNames(this.handshake.capabilityFlags).join(', ')
  109. );
  110. }
  111. connection.serverCapabilityFlags = this.handshake.capabilityFlags;
  112. connection.serverEncoding = CharsetToEncoding[this.handshake.characterSet];
  113. connection.connectionId = this.handshake.connectionId;
  114. const serverSSLSupport =
  115. this.handshake.capabilityFlags & ClientConstants.SSL;
  116. // multi factor authentication is enabled with the
  117. // "MULTI_FACTOR_AUTHENTICATION" capability and should only be used if it
  118. // is supported by the server
  119. const multiFactorAuthentication =
  120. this.handshake.capabilityFlags & ClientConstants.MULTI_FACTOR_AUTHENTICATION;
  121. this.clientFlags = this.clientFlags | multiFactorAuthentication;
  122. // use compression only if requested by client and supported by server
  123. connection.config.compress =
  124. connection.config.compress &&
  125. this.handshake.capabilityFlags & ClientConstants.COMPRESS;
  126. this.clientFlags = this.clientFlags | connection.config.compress;
  127. if (connection.config.ssl) {
  128. // client requires SSL but server does not support it
  129. if (!serverSSLSupport) {
  130. const err = new Error('Server does not support secure connection');
  131. err.code = 'HANDSHAKE_NO_SSL_SUPPORT';
  132. err.fatal = true;
  133. this.emit('error', err);
  134. return false;
  135. }
  136. // send ssl upgrade request and immediately upgrade connection to secure
  137. this.clientFlags |= ClientConstants.SSL;
  138. this.sendSSLRequest(connection);
  139. connection.startTLS(err => {
  140. // after connection is secure
  141. if (err) {
  142. // SSL negotiation error are fatal
  143. err.code = 'HANDSHAKE_SSL_ERROR';
  144. err.fatal = true;
  145. this.emit('error', err);
  146. return;
  147. }
  148. // rest of communication is encrypted
  149. this.sendCredentials(connection);
  150. });
  151. } else {
  152. this.sendCredentials(connection);
  153. }
  154. if (multiFactorAuthentication) {
  155. // if the server supports multi-factor authentication, we enable it in
  156. // the client
  157. this.authenticationFactor = 1;
  158. }
  159. return ClientHandshake.prototype.handshakeResult;
  160. }
  161. handshakeResult(packet, connection) {
  162. const marker = packet.peekByte();
  163. // packet can be OK_Packet, ERR_Packet, AuthSwitchRequest, AuthNextFactor
  164. // or AuthMoreData
  165. if (marker === 0xfe || marker === 1 || marker === 0x02) {
  166. const authSwitch = require('./auth_switch');
  167. try {
  168. if (marker === 1) {
  169. authSwitch.authSwitchRequestMoreData(packet, connection, this);
  170. } else {
  171. // if authenticationFactor === 0, it means the server does not support
  172. // the multi-factor authentication capability
  173. if (this.authenticationFactor !== 0) {
  174. // if we are past the first authentication factor, we should use the
  175. // corresponding password (if there is one)
  176. connection.config.password = this[`password${this.authenticationFactor}`];
  177. // update the current authentication factor
  178. this.authenticationFactor += 1;
  179. }
  180. // if marker === 0x02, it means it is an AuthNextFactor packet,
  181. // which is similar in structure to an AuthSwitchRequest packet,
  182. // so, we can use it directly
  183. authSwitch.authSwitchRequest(packet, connection, this);
  184. }
  185. return ClientHandshake.prototype.handshakeResult;
  186. } catch (err) {
  187. // Authentication errors are fatal
  188. err.code = 'AUTH_SWITCH_PLUGIN_ERROR';
  189. err.fatal = true;
  190. if (this.onResult) {
  191. this.onResult(err);
  192. } else {
  193. this.emit('error', err);
  194. }
  195. return null;
  196. }
  197. }
  198. if (marker !== 0) {
  199. const err = new Error('Unexpected packet during handshake phase');
  200. // Unknown handshake errors are fatal
  201. err.code = 'HANDSHAKE_UNKNOWN_ERROR';
  202. err.fatal = true;
  203. if (this.onResult) {
  204. this.onResult(err);
  205. } else {
  206. this.emit('error', err);
  207. }
  208. return null;
  209. }
  210. // this should be called from ClientHandshake command only
  211. // and skipped when called from ChangeUser command
  212. if (!connection.authorized) {
  213. connection.authorized = true;
  214. if (connection.config.compress) {
  215. const enableCompression = require('../compressed_protocol.js')
  216. .enableCompression;
  217. enableCompression(connection);
  218. }
  219. }
  220. if (this.onResult) {
  221. this.onResult(null);
  222. }
  223. return null;
  224. }
  225. }
  226. module.exports = ClientHandshake;