Skip to content

Commit bd90d1d

Browse files
committed
Merge pull request #729 from felixge/ssl
SSL support
2 parents 582c204 + 060185d commit bd90d1d

File tree

8 files changed

+125
-9
lines changed

8 files changed

+125
-9
lines changed

Changes.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ you spot any mistakes.
77
## HEAD
88

99
* Add `connectTimeout` option to specify a timeout for establishing a connection #726
10+
* SSL support #481
1011

1112
## v2.0.1
1213

Readme.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,9 @@ issue [#501](https://github.com/felixge/node-mysql/issues/501). (Default: `'fals
167167
with this, it exposes you to SQL injection attacks. (Default: `false`)
168168
* `flags`: List of connection flags to use other than the default ones. It is
169169
also possible to blacklist default ones. For more information, check [Connection Flags](#connection-flags).
170+
* `ssl`: object with ssl parameters ( same format as [crypto.createCredentials](http://nodejs.org/api/crypto.html#crypto_crypto_createcredentials_details) argument )
171+
or a string containing name of ssl profile. Currently only 'Amazon RDS' profile is bundled, containing CA from https://rds.amazonaws.com/doc/rds-ssl-ca-cert.pem
172+
170173

171174
In addition to passing these options as an object, you can also use a url
172175
string. For example:

fixtures/ssl-profiles.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"Amazon RDS": {"ca":"-----BEGIN CERTIFICATE-----\nMIIDQzCCAqygAwIBAgIJAOd1tlfiGoEoMA0GCSqGSIb3DQEBBQUAMHUxCzAJBgNV\nBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdTZWF0dGxlMRMw\nEQYDVQQKEwpBbWF6b24uY29tMQwwCgYDVQQLEwNSRFMxHDAaBgNVBAMTE2F3cy5h\nbWF6b24uY29tL3Jkcy8wHhcNMTAwNDA1MjI0NDMxWhcNMTUwNDA0MjI0NDMxWjB1\nMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHU2Vh\ndHRsZTETMBEGA1UEChMKQW1hem9uLmNvbTEMMAoGA1UECxMDUkRTMRwwGgYDVQQD\nExNhd3MuYW1hem9uLmNvbS9yZHMvMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB\ngQDKhXGU7tizxUR5WaFoMTFcxNxa05PEjZaIOEN5ctkWrqYSRov0/nOMoZjqk8bC\nmed9vPFoQGD0OTakPs0jVe3wwmR735hyVwmKIPPsGlaBYj1O6llIpZeQVyupNx56\nUzqtiLaDzh1KcmfqP3qP2dInzBfJQKjiRudo1FWnpPt33QIDAQABo4HaMIHXMB0G\nA1UdDgQWBBT/H3x+cqSkR/ePSIinPtc4yWKe3DCBpwYDVR0jBIGfMIGcgBT/H3x+\ncqSkR/ePSIinPtc4yWKe3KF5pHcwdTELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldh\nc2hpbmd0b24xEDAOBgNVBAcTB1NlYXR0bGUxEzARBgNVBAoTCkFtYXpvbi5jb20x\nDDAKBgNVBAsTA1JEUzEcMBoGA1UEAxMTYXdzLmFtYXpvbi5jb20vcmRzL4IJAOd1\ntlfiGoEoMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEAvguZy/BDT66x\nGfgnJlyQwnFSeVLQm9u/FIvz4huGjbq9dqnD6h/Gm56QPFdyMEyDiZWaqY6V08lY\nLTBNb4kcIc9/6pc0/ojKciP5QJRm6OiZ4vgG05nF4fYjhU7WClUx7cxq1fKjNc2J\nUCmmYqgiVkAGWRETVo+byOSDZ4swb10=\n-----END CERTIFICATE-----\n"}
3+
4+
}

lib/Connection.js

Lines changed: 56 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -64,11 +64,19 @@ Connection.prototype.connect = function(cb) {
6464
? Net.createConnection(this.config.socketPath)
6565
: Net.createConnection(this.config);
6666

67-
// Node v0.10+ Switch socket into "old mode" (Streams2)
68-
this._socket.on("data",function() {});
69-
70-
this._socket.pipe(this._protocol);
71-
this._protocol.pipe(this._socket);
67+
var connection = this;
68+
this._protocol.on('data', function(data) {
69+
connection._socket.write(data);
70+
});
71+
this._socket.on('data', function(data) {
72+
connection._protocol.write(data);
73+
});
74+
this._protocol.on('end', function() {
75+
connection._socket.end()
76+
});
77+
this._socket.on('end', function(err) {
78+
connection._protocol.end();
79+
});
7280

7381
this._socket.on('error', this._handleNetworkError.bind(this));
7482
this._socket.on('connect', this._handleProtocolConnect.bind(this));
@@ -200,6 +208,49 @@ Connection.prototype.format = function(sql, values) {
200208
return SqlString.format(sql, values, this.config.stringifyObjects, this.config.timezone);
201209
};
202210

211+
212+
Connection.prototype._startTLS = function(onSecure) {
213+
214+
var crypto = require('crypto');
215+
var tls = require('tls');
216+
var sslProfiles, sslProfileName;
217+
if (typeof this.config.ssl == 'string') {
218+
sslProfileName = this.config.ssl;
219+
sslProfiles = require('../fixtures/ssl-profiles.json');
220+
this.config.ssl = sslProfiles[this.config.ssl];
221+
if (!this.config.ssl)
222+
throw new Error('Unknown SSL profile for ' + sslProfileName);
223+
}
224+
225+
// before TLS:
226+
// _socket <-> _protocol
227+
// after:
228+
// _socket <-> securePair.encrypted <-> securePair.cleartext <-> _protocol
229+
230+
var credentials = crypto.createCredentials({
231+
key: this.config.ssl.key,
232+
cert: this.config.ssl.cert,
233+
passphrase: this.config.ssl.passphrase,
234+
ca: this.config.ssl.ca
235+
});
236+
237+
var securePair = tls.createSecurePair(credentials, false);
238+
239+
securePair.encrypted.pipe(this._socket);
240+
securePair.cleartext.pipe(this._protocol);
241+
242+
// TODO: change to unpipe/pipe (does not work for some reason. Streams1/2 conflict?)
243+
this._socket.removeAllListeners('data');
244+
this._protocol.removeAllListeners('data');
245+
this._socket.on('data', function(data) {
246+
securePair.encrypted.write(data);
247+
});
248+
this._protocol.on('data', function(data) {
249+
securePair.cleartext.write(data);
250+
});
251+
securePair.on('secure', onSecure);
252+
};
253+
203254
Connection.prototype._handleConnectTimeout = function() {
204255
if (this._socket) {
205256
this._socket.setTimeout(0);

lib/ConnectionConfig.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ function ConnectionConfig(options) {
2727
this.flags = options.flags || '';
2828
this.queryFormat = options.queryFormat;
2929
this.pool = options.pool || undefined;
30+
this.ssl = options.ssl || undefined;
3031
this.multipleStatements = options.multipleStatements || false;
3132
this.typeCast = (options.typeCast === undefined)
3233
? true

lib/protocol/Protocol.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,11 @@ Protocol.prototype._enqueue = function(sequence) {
122122
})
123123
.on('end', function() {
124124
self._dequeue();
125+
})
126+
.on('start-tls', function() {
127+
self._connection._startTLS(function() {
128+
sequence._tlsUpgradeCompleteHandler();
129+
})
125130
});
126131

127132
if (this._queue.length === 1) {
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// http://dev.mysql.com/doc/internals/en/ssl.html
2+
// http://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::SSLRequest
3+
4+
var ClientConstants = require('../constants/client');
5+
6+
module.exports = SSLRequestPacket;
7+
8+
function SSLRequestPacket(options) {
9+
options = options || {};
10+
this.clientFlags = options.clientFlags | ClientConstants.CLIENT_SSL;
11+
this.maxPacketSize = options.maxPacketSize;
12+
this.charsetNumber = options.charsetNumber;
13+
}
14+
15+
SSLRequestPacket.prototype.parse = function(parser) {
16+
// TODO: check SSLRequest packet v41 vs pre v41
17+
this.clientFlags = parser.parseUnsignedNumber(4);
18+
this.maxPacketSize = parser.parseUnsignedNumber(4);
19+
this.charsetNumber = parser.parseUnsignedNumber(1);
20+
};
21+
22+
SSLRequestPacket.prototype.write = function(writer) {
23+
writer.writeUnsignedNumber(4, this.clientFlags);
24+
writer.writeUnsignedNumber(4, this.maxPacketSize);
25+
writer.writeUnsignedNumber(1, this.charsetNumber);
26+
writer.writeFiller(23);
27+
};

lib/protocol/sequences/Handshake.js

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
var Sequence = require('./Sequence');
2-
var Util = require('util');
3-
var Packets = require('../packets');
4-
var Auth = require('../Auth');
1+
var Sequence = require('./Sequence');
2+
var Util = require('util');
3+
var Packets = require('../packets');
4+
var Auth = require('../Auth');
5+
var ClientConstants = require('../constants/client');
56

67
module.exports = Handshake;
78
Util.inherits(Handshake, Sequence);
@@ -31,6 +32,29 @@ Handshake.prototype['HandshakeInitializationPacket'] = function(packet) {
3132

3233
this._config.protocol41 = packet.protocol41;
3334

35+
var serverSSLSupport = packet.serverCapabilities1 & ClientConstants.CLIENT_SSL;
36+
37+
if (this._config.ssl) {
38+
if (!serverSSLSupport)
39+
throw new Error('Server does not support secure connnection');
40+
this._config.clientFlags |= ClientConstants.CLIENT_SSL;
41+
this.emit('packet', new Packets.SSLRequestPacket({
42+
clientFlags : this._config.clientFlags,
43+
maxPacketSize : this._config.maxPacketSize,
44+
charsetNumber : this._config.charsetNumber
45+
}));
46+
this.emit('start-tls');
47+
} else {
48+
this._sendCredentials();
49+
}
50+
};
51+
52+
Handshake.prototype._tlsUpgradeCompleteHandler = function() {
53+
this._sendCredentials();
54+
};
55+
56+
Handshake.prototype._sendCredentials = function(serverHello) {
57+
var packet = this._handshakeInitializationPacket;
3458
this.emit('packet', new Packets.ClientAuthenticationPacket({
3559
clientFlags : this._config.clientFlags,
3660
maxPacketSize : this._config.maxPacketSize,

0 commit comments

Comments
 (0)