Skip to content

Commit b57e303

Browse files
committed
Implement pluggable authentication.
Send the CLIENT_PLUGIN_AUTH flag when negotiating the initial connection and supply the auth plugin name in the packets that need it when this client flag is set.
1 parent e3e123e commit b57e303

File tree

6 files changed

+62
-4
lines changed

6 files changed

+62
-4
lines changed

Readme.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -1319,6 +1319,7 @@ The following flags are sent by default on a new connection:
13191319
- `LONG_PASSWORD` - Use the improved version of Old Password Authentication.
13201320
- `MULTI_RESULTS` - Can handle multiple resultsets for COM_QUERY.
13211321
- `ODBC` Old; no effect.
1322+
- `PLUGIN_AUTH` - Supports auth plugins (specifically, `mysql_old_password` and `mysql_native_password`)
13221323
- `PROTOCOL_41` - Uses the 4.1 protocol.
13231324
- `PS_MULTI_RESULTS` - Can handle multiple resultsets for COM_STMT_EXECUTE.
13241325
- `RESERVED` - Old flag for the 4.1 protocol.
@@ -1339,7 +1340,6 @@ available to specify.
13391340
- COMPRESS
13401341
- INTERACTIVE
13411342
- NO_SCHEMA
1342-
- PLUGIN_AUTH
13431343
- REMEMBER_OPTIONS
13441344
- SSL
13451345
- SSL_VERIFY_SERVER_CERT

lib/ConnectionConfig.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ ConnectionConfig.getDefaultFlags = function getDefaultFlags(options) {
106106
'+LONG_PASSWORD', // Use the improved version of Old Password Authentication
107107
'+MULTI_RESULTS', // Can handle multiple resultsets for COM_QUERY
108108
'+ODBC', // Special handling of ODBC behaviour
109-
'-PLUGIN_AUTH', // Does *NOT* support auth plugins
109+
'+PLUGIN_AUTH', // Supports auth plugins
110110
'+PROTOCOL_41', // Uses the 4.1 protocol
111111
'+PS_MULTI_RESULTS', // Can handle multiple resultsets for COM_STMT_EXECUTE
112112
'+RESERVED', // Unused

lib/protocol/packets/ClientAuthenticationPacket.js

+3
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ function ClientAuthenticationPacket(options) {
1212
this.scrambleBuff = options.scrambleBuff;
1313
this.database = options.database;
1414
this.protocol41 = options.protocol41;
15+
this.pluginName = options.pluginName;
1516
}
1617

1718
ClientAuthenticationPacket.prototype.parse = function(parser) {
@@ -23,6 +24,7 @@ ClientAuthenticationPacket.prototype.parse = function(parser) {
2324
this.user = parser.parseNullTerminatedString();
2425
this.scrambleBuff = parser.parseLengthCodedBuffer();
2526
this.database = parser.parseNullTerminatedString();
27+
this.pluginName = parser.parseNullTerminatedString();
2628
} else {
2729
this.clientFlags = parser.parseUnsignedNumber(2);
2830
this.maxPacketSize = parser.parseUnsignedNumber(3);
@@ -41,6 +43,7 @@ ClientAuthenticationPacket.prototype.write = function(writer) {
4143
writer.writeNullTerminatedString(this.user);
4244
writer.writeLengthCodedBuffer(this.scrambleBuff);
4345
writer.writeNullTerminatedString(this.database);
46+
writer.writeNullTerminatedString(this.pluginName);
4447
} else {
4548
writer.writeUnsignedNumber(2, this.clientFlags);
4649
writer.writeUnsignedNumber(3, this.maxPacketSize);

lib/protocol/packets/ComChangeUserPacket.js

+4-1
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,16 @@ function ComChangeUserPacket(options) {
77
this.scrambleBuff = options.scrambleBuff;
88
this.database = options.database;
99
this.charsetNumber = options.charsetNumber;
10+
this.pluginName = options.pluginName;
1011
}
1112

1213
ComChangeUserPacket.prototype.parse = function(parser) {
1314
this.command = parser.parseUnsignedNumber(1);
1415
this.user = parser.parseNullTerminatedString();
1516
this.scrambleBuff = parser.parseLengthCodedBuffer();
1617
this.database = parser.parseNullTerminatedString();
17-
this.charsetNumber = parser.parseUnsignedNumber(1);
18+
this.charsetNumber = parser.parseUnsignedNumber(2);
19+
this.pluginName = parser.parseNullTerminatedString();
1820
};
1921

2022
ComChangeUserPacket.prototype.write = function(writer) {
@@ -23,4 +25,5 @@ ComChangeUserPacket.prototype.write = function(writer) {
2325
writer.writeLengthCodedBuffer(this.scrambleBuff);
2426
writer.writeNullTerminatedString(this.database);
2527
writer.writeUnsignedNumber(2, this.charsetNumber);
28+
writer.writeNullTerminatedString(this.pluginName);
2629
};

lib/protocol/sequences/ChangeUser.js

+52-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ ChangeUser.prototype.start = function(handshakeInitializationPacket) {
2323
user : this._user,
2424
scrambleBuff : scrambleBuff,
2525
database : this._database,
26-
charsetNumber : this._charsetNumber
26+
charsetNumber : this._charsetNumber,
27+
pluginName : 'mysql_native_password'
2728
});
2829

2930
this._currentConfig.user = this._user;
@@ -34,8 +35,58 @@ ChangeUser.prototype.start = function(handshakeInitializationPacket) {
3435
this.emit('packet', packet);
3536
};
3637

38+
ChangeUser.prototype.determinePacket = function(firstByte) {
39+
if (firstByte === 0xfe) {
40+
return Packets.UseOldPasswordPacket;
41+
}
42+
43+
return undefined;
44+
};
45+
3746
ChangeUser.prototype['ErrorPacket'] = function(packet) {
3847
var err = this._packetToError(packet);
3948
err.fatal = true;
4049
this.end(err);
4150
};
51+
52+
ChangeUser.prototype['UseOldPasswordPacket'] = function(packet) {
53+
if (!packet || !packet.methodName || packet.methodName === 'mysql_old_password') {
54+
if (!this._currentConfig.insecureAuth) {
55+
var err = new Error(
56+
'MySQL server is requesting the old and insecure pre-4.1 auth mechanism.' +
57+
'Upgrade the user password or use the {insecureAuth: true} option.'
58+
);
59+
60+
err.code = 'HANDSHAKE_INSECURE_AUTH';
61+
err.fatal = true;
62+
63+
this.end(err);
64+
return;
65+
}
66+
67+
this.emit('packet', new Packets.OldPasswordPacket({
68+
scrambleBuff: Auth.scramble323(packet.pluginData || this._handshakeInitializationPacket.scrambleBuff(), this._currentConfig.password)
69+
}));
70+
} else if (packet.methodName === 'mysql_native_password') {
71+
// "auth plugin data" is documented as "string[EOF]", but MySQL Server will send a
72+
// null-terminated byte array for mysql_native_password; we only need to hash with
73+
// the first 20 bytes
74+
var challenge = packet.pluginData;
75+
if (challenge.length === 21 && challenge[20] === 0) {
76+
challenge = challenge.slice(0, 20);
77+
}
78+
79+
this.emit('packet', new Packets.AuthenticationSwitchResponsePacket({
80+
scrambleBuff: Auth.token(this._currentConfig.password, challenge)
81+
}));
82+
} else {
83+
var err = new Error(
84+
'MySQL is requesting the ' + packet.methodName + ' authentication method, which is not supported.'
85+
);
86+
87+
err.code = 'UNSUPPORTED_AUTH_METHOD';
88+
err.fatal = true;
89+
90+
this.end(err);
91+
}
92+
};

lib/protocol/sequences/Handshake.js

+1
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ Handshake.prototype._sendCredentials = function() {
7474
user : this._config.user,
7575
database : this._config.database,
7676
protocol41 : packet.protocol41,
77+
pluginName : (packet.protocol41) ? 'mysql_native_password' : '',
7778
scrambleBuff : (packet.protocol41)
7879
? Auth.token(this._config.password, packet.scrambleBuff())
7980
: Auth.scramble323(packet.scrambleBuff(), this._config.password)

0 commit comments

Comments
 (0)