Skip to content

Commit 0517aad

Browse files
bgraingerdougwilson
authored andcommitted
Support mysql_native_password auth switch request for Azure
fixes #1396 fixes #1729 closes #1730
1 parent 525960c commit 0517aad

9 files changed

+156
-7
lines changed

Changes.md

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ you spot any mistakes.
77
## HEAD
88

99
* Fix typo in insecure auth error message
10+
* Support `mysql_native_password` auth switch request for Azure #1396 #1729 #1730
1011

1112
## v2.14.1 (2017-08-01)
1213

lib/protocol/Parser.js

+5
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,11 @@ Parser.prototype._nullByteOffset = function() {
309309
return offset;
310310
};
311311

312+
Parser.prototype.parsePacketTerminatedBuffer = function parsePacketTerminatedBuffer() {
313+
var length = this._packetEnd - this._offset;
314+
return this.parseBuffer(length);
315+
};
316+
312317
Parser.prototype.parsePacketTerminatedString = function() {
313318
var length = this._packetEnd - this._offset;
314319
return this.parseString(length);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
module.exports = AuthSwitchRequestPacket;
2+
function AuthSwitchRequestPacket(options) {
3+
options = options || {};
4+
5+
this.status = 0xfe;
6+
this.authMethodName = options.authMethodName;
7+
this.authMethodData = options.authMethodData;
8+
}
9+
10+
AuthSwitchRequestPacket.prototype.parse = function parse(parser) {
11+
this.status = parser.parseUnsignedNumber(1);
12+
this.authMethodName = parser.parseNullTerminatedString();
13+
this.authMethodData = parser.parsePacketTerminatedBuffer();
14+
};
15+
16+
AuthSwitchRequestPacket.prototype.write = function write(writer) {
17+
writer.writeUnsignedNumber(1, this.status);
18+
writer.writeNullTerminatedString(this.authMethodName);
19+
writer.writeBuffer(this.authMethodData);
20+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
module.exports = AuthSwitchResponsePacket;
2+
function AuthSwitchResponsePacket(options) {
3+
options = options || {};
4+
5+
this.data = options.data;
6+
}
7+
8+
AuthSwitchResponsePacket.prototype.parse = function parse(parser) {
9+
this.data = parser.parsePacketTerminatedBuffer();
10+
};
11+
12+
AuthSwitchResponsePacket.prototype.write = function write(writer) {
13+
writer.writeBuffer(this.data);
14+
};

lib/protocol/packets/index.js

+2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
exports.AuthSwitchRequestPacket = require('./AuthSwitchRequestPacket');
2+
exports.AuthSwitchResponsePacket = require('./AuthSwitchResponsePacket');
13
exports.ClientAuthenticationPacket = require('./ClientAuthenticationPacket');
24
exports.ComChangeUserPacket = require('./ComChangeUserPacket');
35
exports.ComPingPacket = require('./ComPingPacket');

lib/protocol/sequences/Handshake.js

+29-2
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ function Handshake(options, callback) {
1515
this._handshakeInitializationPacket = null;
1616
}
1717

18-
Handshake.prototype.determinePacket = function(firstByte) {
18+
Handshake.prototype.determinePacket = function determinePacket(firstByte, parser) {
1919
if (firstByte === 0xff) {
2020
return Packets.ErrorPacket;
2121
}
@@ -25,12 +25,39 @@ Handshake.prototype.determinePacket = function(firstByte) {
2525
}
2626

2727
if (firstByte === 0xfe) {
28-
return Packets.UseOldPasswordPacket;
28+
return (parser.reachedPacketEnd())
29+
? Packets.UseOldPasswordPacket
30+
: Packets.AuthSwitchRequestPacket;
2931
}
3032

3133
return undefined;
3234
};
3335

36+
Handshake.prototype['AuthSwitchRequestPacket'] = function (packet) {
37+
switch (packet.authMethodName) {
38+
case 'mysql_native_password':
39+
case 'mysql_old_password':
40+
default:
41+
}
42+
43+
if (packet.authMethodName === 'mysql_native_password') {
44+
var challenge = packet.authMethodData.slice(0, 20);
45+
46+
this.emit('packet', new Packets.AuthSwitchResponsePacket({
47+
data: Auth.token(this._config.password, challenge)
48+
}));
49+
} else {
50+
var err = new Error(
51+
'MySQL is requesting the ' + packet.authMethodName + ' authentication method, which is not supported.'
52+
);
53+
54+
err.code = 'UNSUPPORTED_AUTH_METHOD';
55+
err.fatal = true;
56+
57+
this.end(err);
58+
}
59+
};
60+
3461
Handshake.prototype['HandshakeInitializationPacket'] = function(packet) {
3562
this._handshakeInitializationPacket = packet;
3663

test/FakeServer.js

+17-5
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ function FakeConnection(socket) {
6464
this._handshakeInitializationPacket = null;
6565
this._clientAuthenticationPacket = null;
6666
this._oldPasswordPacket = null;
67+
this._authSwitchResponse = null;
6768
this._handshakeOptions = {};
6869

6970
socket.on('data', this._handleData.bind(this));
@@ -91,9 +92,7 @@ FakeConnection.prototype.deny = function(message, errno) {
9192
}));
9293
};
9394

94-
FakeConnection.prototype._sendAuthResponse = function(packet, expected) {
95-
var got = packet.scrambleBuff;
96-
95+
FakeConnection.prototype._sendAuthResponse = function _sendAuthResponse(got, expected) {
9796
if (expected.toString('hex') === got.toString('hex')) {
9897
this._sendPacket(new Packets.OkPacket());
9998
} else {
@@ -269,9 +268,11 @@ FakeConnection.prototype._parsePacket = function(header) {
269268
this._clientAuthenticationPacket = packet;
270269
if (this._handshakeOptions.oldPassword) {
271270
this._sendPacket(new Packets.UseOldPasswordPacket());
271+
} else if (this._handshakeOptions.authMethodName) {
272+
this._sendPacket(new Packets.AuthSwitchRequestPacket(this._handshakeOptions));
272273
} else if (this._handshakeOptions.password === 'passwd') {
273274
var expected = Buffer.from('3DA0ADA7C9E1BB3A110575DF53306F9D2DE7FD09', 'hex');
274-
this._sendAuthResponse(packet, expected);
275+
this._sendAuthResponse(packet.scrambleBuff, expected);
275276
} else if (this._handshakeOptions.user || this._handshakeOptions.password) {
276277
throw new Error('not implemented');
277278
} else {
@@ -287,7 +288,14 @@ FakeConnection.prototype._parsePacket = function(header) {
287288

288289
var expected = Auth.scramble323(this._handshakeInitializationPacket.scrambleBuff(), this._handshakeOptions.password);
289290

290-
this._sendAuthResponse(packet, expected);
291+
this._sendAuthResponse(packet.scrambleBuff, expected);
292+
break;
293+
case Packets.AuthSwitchResponsePacket:
294+
this._authSwitchResponse = packet;
295+
296+
var expected = Auth.token(this._handshakeOptions.password, Buffer.from('00112233445566778899AABBCCDDEEFF01020304', 'hex'));
297+
298+
this._sendAuthResponse(packet.data, expected);
291299
break;
292300
case Packets.ComQueryPacket:
293301
if (!this.emit('query', packet)) {
@@ -355,6 +363,10 @@ FakeConnection.prototype._determinePacket = function(header) {
355363
return Packets.OldPasswordPacket;
356364
}
357365

366+
if (this._handshakeOptions.authMethodName && !this._authSwitchResponse) {
367+
return Packets.AuthSwitchResponsePacket;
368+
}
369+
358370
var firstByte = this._parser.peak();
359371
switch (firstByte) {
360372
case 0x01: return Packets.ComQuitPacket;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
var assert = require('assert');
2+
var Buffer = require('safe-buffer').Buffer;
3+
var common = require('../../common');
4+
var connection = common.createConnection({
5+
port : common.fakeServerPort,
6+
password : 'authswitch'
7+
});
8+
9+
var server = common.createFakeServer();
10+
11+
var connected;
12+
server.listen(common.fakeServerPort, function (err) {
13+
assert.ifError(err);
14+
15+
connection.connect(function (err, result) {
16+
assert.ifError(err);
17+
18+
connected = result;
19+
20+
connection.destroy();
21+
server.destroy();
22+
});
23+
});
24+
25+
server.on('connection', function(incomingConnection) {
26+
incomingConnection.handshake({
27+
user : connection.config.user,
28+
password : connection.config.password,
29+
authMethodName : 'mysql_native_password',
30+
authMethodData : Buffer.from('00112233445566778899AABBCCDDEEFF0102030400', 'hex')
31+
});
32+
});
33+
34+
process.on('exit', function() {
35+
assert.equal(connected.fieldCount, 0);
36+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
var assert = require('assert');
2+
var Buffer = require('safe-buffer').Buffer;
3+
var common = require('../../common');
4+
var connection = common.createConnection({
5+
port : common.fakeServerPort,
6+
password : 'authswitch'
7+
});
8+
9+
var server = common.createFakeServer();
10+
11+
server.listen(common.fakeServerPort, function (err) {
12+
assert.ifError(err);
13+
14+
connection.connect(function (err) {
15+
assert.ok(err);
16+
assert.equal(err.code, 'UNSUPPORTED_AUTH_METHOD');
17+
assert.equal(err.fatal, true);
18+
assert.ok(/foo_plugin_password/.test(err.message));
19+
20+
connection.destroy();
21+
server.destroy();
22+
});
23+
});
24+
25+
server.on('connection', function(incomingConnection) {
26+
incomingConnection.handshake({
27+
user : connection.config.user,
28+
password : connection.config.password,
29+
authMethodName : 'foo_plugin_password',
30+
authMethodData : Buffer.alloc(0)
31+
});
32+
});

0 commit comments

Comments
 (0)