macgyver 0.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +5 -0
- data/Gemfile +4 -0
- data/LICENSE +20 -0
- data/README.md +45 -0
- data/Rakefile +1 -0
- data/assets/MacGap.app/Contents/Frameworks/Growl.framework/Growl +0 -0
- data/assets/MacGap.app/Contents/Frameworks/Growl.framework/Versions/A/Growl +0 -0
- data/assets/MacGap.app/Contents/Frameworks/Growl.framework/Versions/A/Headers/Growl.h +5 -0
- data/assets/MacGap.app/Contents/Frameworks/Growl.framework/Versions/A/Headers/GrowlApplicationBridge.h +551 -0
- data/assets/MacGap.app/Contents/Frameworks/Growl.framework/Versions/A/Headers/GrowlDefines.h +341 -0
- data/assets/MacGap.app/Contents/Frameworks/Growl.framework/Versions/A/Resources/Info.plist +40 -0
- data/assets/MacGap.app/Contents/Frameworks/Growl.framework/Versions/A/_CodeSignature/CodeResources +34 -0
- data/assets/MacGap.app/Contents/Info.plist +48 -0
- data/assets/MacGap.app/Contents/MacOS/MacGap +0 -0
- data/assets/MacGap.app/Contents/PkgInfo +1 -0
- data/assets/MacGap.app/Contents/Resources/_debugger.js +1718 -0
- data/assets/MacGap.app/Contents/Resources/_http_agent.js +310 -0
- data/assets/MacGap.app/Contents/Resources/_http_client.js +533 -0
- data/assets/MacGap.app/Contents/Resources/_http_common.js +222 -0
- data/assets/MacGap.app/Contents/Resources/_http_incoming.js +194 -0
- data/assets/MacGap.app/Contents/Resources/_http_outgoing.js +597 -0
- data/assets/MacGap.app/Contents/Resources/_http_server.js +510 -0
- data/assets/MacGap.app/Contents/Resources/_linklist.js +76 -0
- data/assets/MacGap.app/Contents/Resources/_stream_duplex.js +69 -0
- data/assets/MacGap.app/Contents/Resources/_stream_passthrough.js +41 -0
- data/assets/MacGap.app/Contents/Resources/_stream_readable.js +900 -0
- data/assets/MacGap.app/Contents/Resources/_stream_transform.js +204 -0
- data/assets/MacGap.app/Contents/Resources/_stream_writable.js +456 -0
- data/assets/MacGap.app/Contents/Resources/_tls_legacy.js +887 -0
- data/assets/MacGap.app/Contents/Resources/_tls_wrap.js +831 -0
- data/assets/MacGap.app/Contents/Resources/application.icns +0 -0
- data/assets/MacGap.app/Contents/Resources/assert.js +326 -0
- data/assets/MacGap.app/Contents/Resources/buffer.js +724 -0
- data/assets/MacGap.app/Contents/Resources/child_process.js +1107 -0
- data/assets/MacGap.app/Contents/Resources/cluster.js +613 -0
- data/assets/MacGap.app/Contents/Resources/console.js +108 -0
- data/assets/MacGap.app/Contents/Resources/constants.js +22 -0
- data/assets/MacGap.app/Contents/Resources/crypto.js +691 -0
- data/assets/MacGap.app/Contents/Resources/dgram.js +459 -0
- data/assets/MacGap.app/Contents/Resources/dns.js +274 -0
- data/assets/MacGap.app/Contents/Resources/domain.js +292 -0
- data/assets/MacGap.app/Contents/Resources/en.lproj/Credits.rtf +29 -0
- data/assets/MacGap.app/Contents/Resources/en.lproj/InfoPlist.strings +0 -0
- data/assets/MacGap.app/Contents/Resources/en.lproj/MainMenu.nib +0 -0
- data/assets/MacGap.app/Contents/Resources/en.lproj/Window.nib +0 -0
- data/assets/MacGap.app/Contents/Resources/events.js +312 -0
- data/assets/MacGap.app/Contents/Resources/freelist.js +43 -0
- data/assets/MacGap.app/Contents/Resources/fs.js +1732 -0
- data/assets/MacGap.app/Contents/Resources/http.js +119 -0
- data/assets/MacGap.app/Contents/Resources/https.js +134 -0
- data/assets/MacGap.app/Contents/Resources/module.js +529 -0
- data/assets/MacGap.app/Contents/Resources/net.js +1378 -0
- data/assets/MacGap.app/Contents/Resources/nodelike.js +195 -0
- data/assets/MacGap.app/Contents/Resources/os.js +64 -0
- data/assets/MacGap.app/Contents/Resources/path.js +517 -0
- data/assets/MacGap.app/Contents/Resources/public/index.html +38 -0
- data/assets/MacGap.app/Contents/Resources/punycode.js +507 -0
- data/assets/MacGap.app/Contents/Resources/querystring.js +206 -0
- data/assets/MacGap.app/Contents/Resources/readline.js +1311 -0
- data/assets/MacGap.app/Contents/Resources/repl.js +945 -0
- data/assets/MacGap.app/Contents/Resources/smalloc.js +90 -0
- data/assets/MacGap.app/Contents/Resources/stream.js +127 -0
- data/assets/MacGap.app/Contents/Resources/string_decoder.js +189 -0
- data/assets/MacGap.app/Contents/Resources/sys.js +24 -0
- data/assets/MacGap.app/Contents/Resources/timers.js +568 -0
- data/assets/MacGap.app/Contents/Resources/tls.js +220 -0
- data/assets/MacGap.app/Contents/Resources/tty.js +129 -0
- data/assets/MacGap.app/Contents/Resources/url.js +693 -0
- data/assets/MacGap.app/Contents/Resources/util.js +688 -0
- data/assets/MacGap.app/Contents/Resources/vm.js +73 -0
- data/assets/MacGap.app/Contents/Resources/zlib.js +524 -0
- data/assets/index.html +38 -0
- data/bin/macgyver +104 -0
- data/macgyver.gemspec +19 -0
- data/test/public/index.html +27 -0
- metadata +121 -0
@@ -0,0 +1,831 @@
|
|
1
|
+
// Copyright Joyent, Inc. and other Node contributors.
|
2
|
+
//
|
3
|
+
// Permission is hereby granted, free of charge, to any person obtaining a
|
4
|
+
// copy of this software and associated documentation files (the
|
5
|
+
// "Software"), to deal in the Software without restriction, including
|
6
|
+
// without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
// distribute, sublicense, and/or sell copies of the Software, and to permit
|
8
|
+
// persons to whom the Software is furnished to do so, subject to the
|
9
|
+
// following conditions:
|
10
|
+
//
|
11
|
+
// The above copyright notice and this permission notice shall be included
|
12
|
+
// in all copies or substantial portions of the Software.
|
13
|
+
//
|
14
|
+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
15
|
+
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
17
|
+
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
18
|
+
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
19
|
+
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
20
|
+
// USE OR OTHER DEALINGS IN THE SOFTWARE.
|
21
|
+
|
22
|
+
var assert = require('assert');
|
23
|
+
var constants = require('constants');
|
24
|
+
var crypto = require('crypto');
|
25
|
+
var net = require('net');
|
26
|
+
var tls = require('tls');
|
27
|
+
var util = require('util');
|
28
|
+
|
29
|
+
var Timer = process.binding('timer_wrap').Timer;
|
30
|
+
var tls_wrap = process.binding('tls_wrap');
|
31
|
+
|
32
|
+
// Lazy load
|
33
|
+
var tls_legacy;
|
34
|
+
|
35
|
+
var debug = util.debuglog('tls');
|
36
|
+
|
37
|
+
function onhandshakestart() {
|
38
|
+
debug('onhandshakestart');
|
39
|
+
|
40
|
+
var self = this;
|
41
|
+
var ssl = self.ssl;
|
42
|
+
var now = Timer.now();
|
43
|
+
|
44
|
+
assert(now >= ssl.lastHandshakeTime);
|
45
|
+
|
46
|
+
if ((now - ssl.lastHandshakeTime) >= tls.CLIENT_RENEG_WINDOW * 1000) {
|
47
|
+
ssl.handshakes = 0;
|
48
|
+
}
|
49
|
+
|
50
|
+
var first = (ssl.lastHandshakeTime === 0);
|
51
|
+
ssl.lastHandshakeTime = now;
|
52
|
+
if (first) return;
|
53
|
+
|
54
|
+
if (++ssl.handshakes > tls.CLIENT_RENEG_LIMIT) {
|
55
|
+
// Defer the error event to the next tick. We're being called from OpenSSL's
|
56
|
+
// state machine and OpenSSL is not re-entrant. We cannot allow the user's
|
57
|
+
// callback to destroy the connection right now, it would crash and burn.
|
58
|
+
setImmediate(function() {
|
59
|
+
var err = new Error('TLS session renegotiation attack detected.');
|
60
|
+
self._tlsError(err);
|
61
|
+
});
|
62
|
+
}
|
63
|
+
}
|
64
|
+
|
65
|
+
|
66
|
+
function onhandshakedone() {
|
67
|
+
// for future use
|
68
|
+
debug('onhandshakedone');
|
69
|
+
this._finishInit();
|
70
|
+
}
|
71
|
+
|
72
|
+
|
73
|
+
function onclienthello(hello) {
|
74
|
+
var self = this,
|
75
|
+
onceSession = false,
|
76
|
+
onceSNI = false;
|
77
|
+
|
78
|
+
function callback(err, session) {
|
79
|
+
if (onceSession)
|
80
|
+
return self.destroy(new Error('TLS session callback was called 2 times'));
|
81
|
+
onceSession = true;
|
82
|
+
|
83
|
+
if (err)
|
84
|
+
return self.destroy(err);
|
85
|
+
|
86
|
+
// NOTE: That we have disabled OpenSSL's internal session storage in
|
87
|
+
// `node_crypto.cc` and hence its safe to rely on getting servername only
|
88
|
+
// from clienthello or this place.
|
89
|
+
var ret = self.ssl.loadSession(session);
|
90
|
+
|
91
|
+
// Servername came from SSL session
|
92
|
+
// NOTE: TLS Session ticket doesn't include servername information
|
93
|
+
//
|
94
|
+
// Another note, From RFC3546:
|
95
|
+
//
|
96
|
+
// If, on the other hand, the older
|
97
|
+
// session is resumed, then the server MUST ignore extensions appearing
|
98
|
+
// in the client hello, and send a server hello containing no
|
99
|
+
// extensions; in this case the extension functionality negotiated
|
100
|
+
// during the original session initiation is applied to the resumed
|
101
|
+
// session.
|
102
|
+
//
|
103
|
+
// Therefore we should account session loading when dealing with servername
|
104
|
+
if (ret && ret.servername) {
|
105
|
+
self._SNICallback(ret.servername, onSNIResult);
|
106
|
+
} else if (hello.servername && self._SNICallback) {
|
107
|
+
self._SNICallback(hello.servername, onSNIResult);
|
108
|
+
} else {
|
109
|
+
self.ssl.endParser();
|
110
|
+
}
|
111
|
+
}
|
112
|
+
|
113
|
+
function onSNIResult(err, context) {
|
114
|
+
if (onceSNI)
|
115
|
+
return self.destroy(new Error('TLS SNI callback was called 2 times'));
|
116
|
+
onceSNI = true;
|
117
|
+
|
118
|
+
if (err)
|
119
|
+
return self.destroy(err);
|
120
|
+
|
121
|
+
if (context)
|
122
|
+
self.ssl.sni_context = context;
|
123
|
+
|
124
|
+
self.ssl.endParser();
|
125
|
+
}
|
126
|
+
|
127
|
+
if (hello.sessionId.length <= 0 ||
|
128
|
+
hello.tlsTicket ||
|
129
|
+
this.server &&
|
130
|
+
!this.server.emit('resumeSession', hello.sessionId, callback)) {
|
131
|
+
// Invoke SNI callback, since we've no session to resume
|
132
|
+
if (hello.servername && this._SNICallback)
|
133
|
+
this._SNICallback(hello.servername, onSNIResult);
|
134
|
+
else
|
135
|
+
this.ssl.endParser();
|
136
|
+
}
|
137
|
+
}
|
138
|
+
|
139
|
+
|
140
|
+
function onnewsession(key, session) {
|
141
|
+
if (this.server)
|
142
|
+
this.server.emit('newSession', key, session);
|
143
|
+
}
|
144
|
+
|
145
|
+
|
146
|
+
/**
|
147
|
+
* Provides a wrap of socket stream to do encrypted communication.
|
148
|
+
*/
|
149
|
+
|
150
|
+
function TLSSocket(socket, options) {
|
151
|
+
// Disallow wrapping TLSSocket in TLSSocket
|
152
|
+
assert(!(socket instanceof TLSSocket));
|
153
|
+
|
154
|
+
net.Socket.call(this, socket && {
|
155
|
+
handle: socket._handle,
|
156
|
+
allowHalfOpen: socket.allowHalfOpen,
|
157
|
+
readable: socket.readable,
|
158
|
+
writable: socket.writable
|
159
|
+
});
|
160
|
+
|
161
|
+
// To prevent assertion in afterConnect()
|
162
|
+
if (socket)
|
163
|
+
this._connecting = socket._connecting;
|
164
|
+
|
165
|
+
this._tlsOptions = options;
|
166
|
+
this._secureEstablished = false;
|
167
|
+
this._controlReleased = false;
|
168
|
+
this._SNICallback = null;
|
169
|
+
this.ssl = null;
|
170
|
+
this.servername = null;
|
171
|
+
this.npnProtocol = null;
|
172
|
+
this.authorized = false;
|
173
|
+
this.authorizationError = null;
|
174
|
+
|
175
|
+
// Just a documented property to make secure sockets
|
176
|
+
// distinguishable from regular ones.
|
177
|
+
this.encrypted = true;
|
178
|
+
|
179
|
+
this.on('error', this._tlsError);
|
180
|
+
|
181
|
+
if (!this._handle) {
|
182
|
+
this.once('connect', function() {
|
183
|
+
this._init(null);
|
184
|
+
});
|
185
|
+
} else {
|
186
|
+
this._init(socket);
|
187
|
+
}
|
188
|
+
}
|
189
|
+
util.inherits(TLSSocket, net.Socket);
|
190
|
+
exports.TLSSocket = TLSSocket;
|
191
|
+
|
192
|
+
TLSSocket.prototype._init = function(socket) {
|
193
|
+
assert(this._handle);
|
194
|
+
|
195
|
+
// lib/net.js expect this value to be non-zero if write hasn't been flushed
|
196
|
+
// immediately
|
197
|
+
// TODO(indutny): rewise this solution, it might be 1 before handshake and
|
198
|
+
// represent real writeQueueSize during regular writes.
|
199
|
+
this._handle.writeQueueSize = 1;
|
200
|
+
|
201
|
+
var self = this;
|
202
|
+
var options = this._tlsOptions;
|
203
|
+
|
204
|
+
// Wrap socket's handle
|
205
|
+
var credentials = options.credentials || crypto.createCredentials();
|
206
|
+
this.ssl = tls_wrap.wrap(this._handle, credentials.context, options.isServer);
|
207
|
+
this.server = options.server || null;
|
208
|
+
|
209
|
+
// For clients, we will always have either a given ca list or be using
|
210
|
+
// default one
|
211
|
+
var requestCert = !!options.requestCert || !options.isServer,
|
212
|
+
rejectUnauthorized = !!options.rejectUnauthorized;
|
213
|
+
|
214
|
+
this._requestCert = requestCert;
|
215
|
+
this._rejectUnauthorized = rejectUnauthorized;
|
216
|
+
if (requestCert || rejectUnauthorized)
|
217
|
+
this.ssl.setVerifyMode(requestCert, rejectUnauthorized);
|
218
|
+
|
219
|
+
if (options.isServer) {
|
220
|
+
this.ssl.onhandshakestart = onhandshakestart.bind(this);
|
221
|
+
this.ssl.onhandshakedone = onhandshakedone.bind(this);
|
222
|
+
this.ssl.onclienthello = onclienthello.bind(this);
|
223
|
+
this.ssl.onnewsession = onnewsession.bind(this);
|
224
|
+
this.ssl.lastHandshakeTime = 0;
|
225
|
+
this.ssl.handshakes = 0;
|
226
|
+
|
227
|
+
if (this.server &&
|
228
|
+
(this.server.listeners('resumeSession').length > 0 ||
|
229
|
+
this.server.listeners('newSession').length > 0)) {
|
230
|
+
this.ssl.enableSessionCallbacks();
|
231
|
+
}
|
232
|
+
} else {
|
233
|
+
this.ssl.onhandshakestart = function() {};
|
234
|
+
this.ssl.onhandshakedone = this._finishInit.bind(this);
|
235
|
+
}
|
236
|
+
|
237
|
+
this.ssl.onerror = function(err) {
|
238
|
+
// Destroy socket if error happened before handshake's finish
|
239
|
+
if (!this._secureEstablished) {
|
240
|
+
self._tlsError(err);
|
241
|
+
self.destroy();
|
242
|
+
} else if (options.isServer &&
|
243
|
+
rejectUnauthorized &&
|
244
|
+
/peer did not return a certificate/.test(err.message)) {
|
245
|
+
// Ignore server's authorization errors
|
246
|
+
self.destroy();
|
247
|
+
} else {
|
248
|
+
// Throw error
|
249
|
+
self._tlsError(err);
|
250
|
+
}
|
251
|
+
};
|
252
|
+
|
253
|
+
// If custom SNICallback was given, or if
|
254
|
+
// there're SNI contexts to perform match against -
|
255
|
+
// set `.onsniselect` callback.
|
256
|
+
if (process.features.tls_sni &&
|
257
|
+
options.isServer &&
|
258
|
+
options.server &&
|
259
|
+
(options.SNICallback !== SNICallback ||
|
260
|
+
options.server._contexts.length)) {
|
261
|
+
assert(typeof options.SNICallback === 'function');
|
262
|
+
this._SNICallback = options.SNICallback;
|
263
|
+
this.ssl.enableHelloParser();
|
264
|
+
}
|
265
|
+
|
266
|
+
if (process.features.tls_npn && options.NPNProtocols)
|
267
|
+
this.ssl.setNPNProtocols(options.NPNProtocols);
|
268
|
+
|
269
|
+
if (options.handshakeTimeout > 0)
|
270
|
+
this.setTimeout(options.handshakeTimeout, this._handleTimeout);
|
271
|
+
|
272
|
+
// Socket already has some buffered data - emulate receiving it
|
273
|
+
if (socket && socket._readableState.length) {
|
274
|
+
var buf;
|
275
|
+
while ((buf = socket.read()) !== null)
|
276
|
+
this.ssl.receive(buf);
|
277
|
+
}
|
278
|
+
};
|
279
|
+
|
280
|
+
TLSSocket.prototype.renegotiate = function(options, callback) {
|
281
|
+
var requestCert = this._requestCert,
|
282
|
+
rejectUnauthorized = this._rejectUnauthorized;
|
283
|
+
|
284
|
+
if (typeof options.requestCert !== 'undefined')
|
285
|
+
requestCert = !!options.requestCert;
|
286
|
+
if (typeof options.rejectUnauthorized !== 'undefined')
|
287
|
+
rejectUnauthorized = !!options.rejectUnauthorized;
|
288
|
+
|
289
|
+
if (requestCert !== this._requestCert ||
|
290
|
+
rejectUnauthorized !== this._rejectUnauthorized) {
|
291
|
+
this.ssl.setVerifyMode(requestCert, rejectUnauthorized);
|
292
|
+
this._requestCert = requestCert;
|
293
|
+
this._rejectUnauthorized = rejectUnauthorized;
|
294
|
+
}
|
295
|
+
if (!this.ssl.renegotiate()) {
|
296
|
+
if (callback) {
|
297
|
+
process.nextTick(function() {
|
298
|
+
callback(new Error('Failed to renegotiate'));
|
299
|
+
});
|
300
|
+
}
|
301
|
+
return false;
|
302
|
+
}
|
303
|
+
|
304
|
+
// Ensure that we'll cycle through internal openssl's state
|
305
|
+
this.write('');
|
306
|
+
|
307
|
+
if (callback) {
|
308
|
+
this.once('secure', function() {
|
309
|
+
callback(null);
|
310
|
+
});
|
311
|
+
}
|
312
|
+
|
313
|
+
return true;
|
314
|
+
};
|
315
|
+
|
316
|
+
TLSSocket.prototype.setMaxSendFragment = function setMaxSendFragment(size) {
|
317
|
+
return this.ssl.setMaxSendFragment(size) == 1;
|
318
|
+
};
|
319
|
+
|
320
|
+
TLSSocket.prototype._handleTimeout = function() {
|
321
|
+
this._tlsError(new Error('TLS handshake timeout'));
|
322
|
+
};
|
323
|
+
|
324
|
+
TLSSocket.prototype._tlsError = function(err) {
|
325
|
+
this.emit('_tlsError', err);
|
326
|
+
if (this._controlReleased)
|
327
|
+
this.emit('error', err);
|
328
|
+
};
|
329
|
+
|
330
|
+
TLSSocket.prototype._releaseControl = function() {
|
331
|
+
if (this._controlReleased)
|
332
|
+
return false;
|
333
|
+
this._controlReleased = true;
|
334
|
+
this.removeListener('error', this._tlsError);
|
335
|
+
return true;
|
336
|
+
};
|
337
|
+
|
338
|
+
TLSSocket.prototype._finishInit = function() {
|
339
|
+
if (process.features.tls_npn) {
|
340
|
+
this.npnProtocol = this.ssl.getNegotiatedProtocol();
|
341
|
+
}
|
342
|
+
|
343
|
+
if (process.features.tls_sni && this._tlsOptions.isServer) {
|
344
|
+
this.servername = this.ssl.getServername();
|
345
|
+
}
|
346
|
+
|
347
|
+
debug('secure established');
|
348
|
+
this._secureEstablished = true;
|
349
|
+
if (this._tlsOptions.handshakeTimeout > 0)
|
350
|
+
this.setTimeout(0, this._handleTimeout);
|
351
|
+
this.emit('secure');
|
352
|
+
};
|
353
|
+
|
354
|
+
TLSSocket.prototype._start = function() {
|
355
|
+
this.ssl.start();
|
356
|
+
};
|
357
|
+
|
358
|
+
TLSSocket.prototype.setServername = function(name) {
|
359
|
+
this.ssl.setServername(name);
|
360
|
+
};
|
361
|
+
|
362
|
+
TLSSocket.prototype.setSession = function(session) {
|
363
|
+
if (util.isString(session))
|
364
|
+
session = new Buffer(session, 'binary');
|
365
|
+
this.ssl.setSession(session);
|
366
|
+
};
|
367
|
+
|
368
|
+
TLSSocket.prototype.getPeerCertificate = function() {
|
369
|
+
if (this.ssl) {
|
370
|
+
var c = this.ssl.getPeerCertificate();
|
371
|
+
|
372
|
+
if (c) {
|
373
|
+
if (c.issuer) c.issuer = tls.parseCertString(c.issuer);
|
374
|
+
if (c.subject) c.subject = tls.parseCertString(c.subject);
|
375
|
+
return c;
|
376
|
+
}
|
377
|
+
}
|
378
|
+
|
379
|
+
return null;
|
380
|
+
};
|
381
|
+
|
382
|
+
TLSSocket.prototype.getSession = function() {
|
383
|
+
if (this.ssl) {
|
384
|
+
return this.ssl.getSession();
|
385
|
+
}
|
386
|
+
|
387
|
+
return null;
|
388
|
+
};
|
389
|
+
|
390
|
+
TLSSocket.prototype.isSessionReused = function() {
|
391
|
+
if (this.ssl) {
|
392
|
+
return this.ssl.isSessionReused();
|
393
|
+
}
|
394
|
+
|
395
|
+
return null;
|
396
|
+
};
|
397
|
+
|
398
|
+
TLSSocket.prototype.getCipher = function(err) {
|
399
|
+
if (this.ssl) {
|
400
|
+
return this.ssl.getCurrentCipher();
|
401
|
+
} else {
|
402
|
+
return null;
|
403
|
+
}
|
404
|
+
};
|
405
|
+
|
406
|
+
// TODO: support anonymous (nocert) and PSK
|
407
|
+
|
408
|
+
|
409
|
+
// AUTHENTICATION MODES
|
410
|
+
//
|
411
|
+
// There are several levels of authentication that TLS/SSL supports.
|
412
|
+
// Read more about this in "man SSL_set_verify".
|
413
|
+
//
|
414
|
+
// 1. The server sends a certificate to the client but does not request a
|
415
|
+
// cert from the client. This is common for most HTTPS servers. The browser
|
416
|
+
// can verify the identity of the server, but the server does not know who
|
417
|
+
// the client is. Authenticating the client is usually done over HTTP using
|
418
|
+
// login boxes and cookies and stuff.
|
419
|
+
//
|
420
|
+
// 2. The server sends a cert to the client and requests that the client
|
421
|
+
// also send it a cert. The client knows who the server is and the server is
|
422
|
+
// requesting the client also identify themselves. There are several
|
423
|
+
// outcomes:
|
424
|
+
//
|
425
|
+
// A) verifyError returns null meaning the client's certificate is signed
|
426
|
+
// by one of the server's CAs. The server know's the client idenity now
|
427
|
+
// and the client is authorized.
|
428
|
+
//
|
429
|
+
// B) For some reason the client's certificate is not acceptable -
|
430
|
+
// verifyError returns a string indicating the problem. The server can
|
431
|
+
// either (i) reject the client or (ii) allow the client to connect as an
|
432
|
+
// unauthorized connection.
|
433
|
+
//
|
434
|
+
// The mode is controlled by two boolean variables.
|
435
|
+
//
|
436
|
+
// requestCert
|
437
|
+
// If true the server requests a certificate from client connections. For
|
438
|
+
// the common HTTPS case, users will want this to be false, which is what
|
439
|
+
// it defaults to.
|
440
|
+
//
|
441
|
+
// rejectUnauthorized
|
442
|
+
// If true clients whose certificates are invalid for any reason will not
|
443
|
+
// be allowed to make connections. If false, they will simply be marked as
|
444
|
+
// unauthorized but secure communication will continue. By default this is
|
445
|
+
// true.
|
446
|
+
//
|
447
|
+
//
|
448
|
+
//
|
449
|
+
// Options:
|
450
|
+
// - requestCert. Send verify request. Default to false.
|
451
|
+
// - rejectUnauthorized. Boolean, default to true.
|
452
|
+
// - key. string.
|
453
|
+
// - cert: string.
|
454
|
+
// - ca: string or array of strings.
|
455
|
+
// - sessionTimeout: integer.
|
456
|
+
//
|
457
|
+
// emit 'secureConnection'
|
458
|
+
// function (tlsSocket) { }
|
459
|
+
//
|
460
|
+
// "UNABLE_TO_GET_ISSUER_CERT", "UNABLE_TO_GET_CRL",
|
461
|
+
// "UNABLE_TO_DECRYPT_CERT_SIGNATURE", "UNABLE_TO_DECRYPT_CRL_SIGNATURE",
|
462
|
+
// "UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY", "CERT_SIGNATURE_FAILURE",
|
463
|
+
// "CRL_SIGNATURE_FAILURE", "CERT_NOT_YET_VALID" "CERT_HAS_EXPIRED",
|
464
|
+
// "CRL_NOT_YET_VALID", "CRL_HAS_EXPIRED" "ERROR_IN_CERT_NOT_BEFORE_FIELD",
|
465
|
+
// "ERROR_IN_CERT_NOT_AFTER_FIELD", "ERROR_IN_CRL_LAST_UPDATE_FIELD",
|
466
|
+
// "ERROR_IN_CRL_NEXT_UPDATE_FIELD", "OUT_OF_MEM",
|
467
|
+
// "DEPTH_ZERO_SELF_SIGNED_CERT", "SELF_SIGNED_CERT_IN_CHAIN",
|
468
|
+
// "UNABLE_TO_GET_ISSUER_CERT_LOCALLY", "UNABLE_TO_VERIFY_LEAF_SIGNATURE",
|
469
|
+
// "CERT_CHAIN_TOO_LONG", "CERT_REVOKED" "INVALID_CA",
|
470
|
+
// "PATH_LENGTH_EXCEEDED", "INVALID_PURPOSE" "CERT_UNTRUSTED",
|
471
|
+
// "CERT_REJECTED"
|
472
|
+
//
|
473
|
+
function Server(/* [options], listener */) {
|
474
|
+
var options, listener;
|
475
|
+
if (util.isObject(arguments[0])) {
|
476
|
+
options = arguments[0];
|
477
|
+
listener = arguments[1];
|
478
|
+
} else if (util.isFunction(arguments[0])) {
|
479
|
+
options = {};
|
480
|
+
listener = arguments[0];
|
481
|
+
}
|
482
|
+
|
483
|
+
if (!(this instanceof Server)) return new Server(options, listener);
|
484
|
+
|
485
|
+
this._contexts = [];
|
486
|
+
|
487
|
+
var self = this;
|
488
|
+
|
489
|
+
// Handle option defaults:
|
490
|
+
this.setOptions(options);
|
491
|
+
|
492
|
+
var sharedCreds = crypto.createCredentials({
|
493
|
+
pfx: self.pfx,
|
494
|
+
key: self.key,
|
495
|
+
passphrase: self.passphrase,
|
496
|
+
cert: self.cert,
|
497
|
+
ca: self.ca,
|
498
|
+
ciphers: self.ciphers || tls.DEFAULT_CIPHERS,
|
499
|
+
ecdhCurve: util.isUndefined(self.ecdhCurve) ?
|
500
|
+
tls.DEFAULT_ECDH_CURVE : self.ecdhCurve,
|
501
|
+
secureProtocol: self.secureProtocol,
|
502
|
+
secureOptions: self.secureOptions,
|
503
|
+
crl: self.crl,
|
504
|
+
sessionIdContext: self.sessionIdContext
|
505
|
+
});
|
506
|
+
this._sharedCreds = sharedCreds;
|
507
|
+
|
508
|
+
var timeout = options.handshakeTimeout || (120 * 1000);
|
509
|
+
|
510
|
+
if (!util.isNumber(timeout)) {
|
511
|
+
throw new TypeError('handshakeTimeout must be a number');
|
512
|
+
}
|
513
|
+
|
514
|
+
if (self.sessionTimeout) {
|
515
|
+
sharedCreds.context.setSessionTimeout(self.sessionTimeout);
|
516
|
+
}
|
517
|
+
|
518
|
+
if (self.ticketKeys) {
|
519
|
+
sharedCreds.context.setTicketKeys(self.ticketKeys);
|
520
|
+
}
|
521
|
+
|
522
|
+
// constructor call
|
523
|
+
net.Server.call(this, function(raw_socket) {
|
524
|
+
var socket = new TLSSocket(raw_socket, {
|
525
|
+
credentials: sharedCreds,
|
526
|
+
isServer: true,
|
527
|
+
server: self,
|
528
|
+
requestCert: self.requestCert,
|
529
|
+
rejectUnauthorized: self.rejectUnauthorized,
|
530
|
+
handshakeTimeout: timeout,
|
531
|
+
NPNProtocols: self.NPNProtocols,
|
532
|
+
SNICallback: options.SNICallback || SNICallback
|
533
|
+
});
|
534
|
+
|
535
|
+
socket.on('secure', function() {
|
536
|
+
if (socket._requestCert) {
|
537
|
+
var verifyError = socket.ssl.verifyError();
|
538
|
+
if (verifyError) {
|
539
|
+
socket.authorizationError = verifyError.code;
|
540
|
+
|
541
|
+
if (socket._rejectUnauthorized)
|
542
|
+
socket.destroy();
|
543
|
+
} else {
|
544
|
+
socket.authorized = true;
|
545
|
+
}
|
546
|
+
}
|
547
|
+
|
548
|
+
if (!socket.destroyed && socket._releaseControl())
|
549
|
+
self.emit('secureConnection', socket);
|
550
|
+
});
|
551
|
+
|
552
|
+
socket.on('_tlsError', function(err) {
|
553
|
+
if (!socket._controlReleased)
|
554
|
+
self.emit('clientError', err, socket);
|
555
|
+
});
|
556
|
+
});
|
557
|
+
|
558
|
+
if (listener) {
|
559
|
+
this.on('secureConnection', listener);
|
560
|
+
}
|
561
|
+
}
|
562
|
+
|
563
|
+
util.inherits(Server, net.Server);
|
564
|
+
exports.Server = Server;
|
565
|
+
exports.createServer = function(options, listener) {
|
566
|
+
return new Server(options, listener);
|
567
|
+
};
|
568
|
+
|
569
|
+
|
570
|
+
Server.prototype._getServerData = function() {
|
571
|
+
return {
|
572
|
+
ticketKeys: this._sharedCreds.context.getTicketKeys().toString('hex')
|
573
|
+
};
|
574
|
+
};
|
575
|
+
|
576
|
+
|
577
|
+
Server.prototype._setServerData = function(data) {
|
578
|
+
this._sharedCreds.context.setTicketKeys(new Buffer(data.ticketKeys, 'hex'));
|
579
|
+
};
|
580
|
+
|
581
|
+
|
582
|
+
Server.prototype.setOptions = function(options) {
|
583
|
+
if (util.isBoolean(options.requestCert)) {
|
584
|
+
this.requestCert = options.requestCert;
|
585
|
+
} else {
|
586
|
+
this.requestCert = false;
|
587
|
+
}
|
588
|
+
|
589
|
+
if (util.isBoolean(options.rejectUnauthorized)) {
|
590
|
+
this.rejectUnauthorized = options.rejectUnauthorized;
|
591
|
+
} else {
|
592
|
+
this.rejectUnauthorized = false;
|
593
|
+
}
|
594
|
+
|
595
|
+
if (options.pfx) this.pfx = options.pfx;
|
596
|
+
if (options.key) this.key = options.key;
|
597
|
+
if (options.passphrase) this.passphrase = options.passphrase;
|
598
|
+
if (options.cert) this.cert = options.cert;
|
599
|
+
if (options.ca) this.ca = options.ca;
|
600
|
+
if (options.secureProtocol) this.secureProtocol = options.secureProtocol;
|
601
|
+
if (options.crl) this.crl = options.crl;
|
602
|
+
if (options.ciphers) this.ciphers = options.ciphers;
|
603
|
+
if (!util.isUndefined(options.ecdhCurve))
|
604
|
+
this.ecdhCurve = options.ecdhCurve;
|
605
|
+
if (options.sessionTimeout) this.sessionTimeout = options.sessionTimeout;
|
606
|
+
var secureOptions = options.secureOptions || 0;
|
607
|
+
if (options.honorCipherOrder) {
|
608
|
+
secureOptions |= constants.SSL_OP_CIPHER_SERVER_PREFERENCE;
|
609
|
+
}
|
610
|
+
if (secureOptions) this.secureOptions = secureOptions;
|
611
|
+
if (options.NPNProtocols) tls.convertNPNProtocols(options.NPNProtocols, this);
|
612
|
+
if (options.sessionIdContext) {
|
613
|
+
this.sessionIdContext = options.sessionIdContext;
|
614
|
+
} else {
|
615
|
+
this.sessionIdContext = crypto.createHash('md5')
|
616
|
+
.update(process.argv.join(' '))
|
617
|
+
.digest('hex');
|
618
|
+
}
|
619
|
+
};
|
620
|
+
|
621
|
+
// SNI Contexts High-Level API
|
622
|
+
Server.prototype.addContext = function(servername, credentials) {
|
623
|
+
if (!servername) {
|
624
|
+
throw 'Servername is required parameter for Server.addContext';
|
625
|
+
}
|
626
|
+
|
627
|
+
var re = new RegExp('^' +
|
628
|
+
servername.replace(/([\.^$+?\-\\[\]{}])/g, '\\$1')
|
629
|
+
.replace(/\*/g, '[^\.]*') +
|
630
|
+
'$');
|
631
|
+
this._contexts.push([re, crypto.createCredentials(credentials).context]);
|
632
|
+
};
|
633
|
+
|
634
|
+
function SNICallback(servername, callback) {
|
635
|
+
var ctx;
|
636
|
+
|
637
|
+
this.server._contexts.some(function(elem) {
|
638
|
+
if (!util.isNull(servername.match(elem[0]))) {
|
639
|
+
ctx = elem[1];
|
640
|
+
return true;
|
641
|
+
}
|
642
|
+
});
|
643
|
+
|
644
|
+
callback(null, ctx);
|
645
|
+
}
|
646
|
+
|
647
|
+
|
648
|
+
// Target API:
|
649
|
+
//
|
650
|
+
// var s = tls.connect({port: 8000, host: "google.com"}, function() {
|
651
|
+
// if (!s.authorized) {
|
652
|
+
// s.destroy();
|
653
|
+
// return;
|
654
|
+
// }
|
655
|
+
//
|
656
|
+
// // s.socket;
|
657
|
+
//
|
658
|
+
// s.end("hello world\n");
|
659
|
+
// });
|
660
|
+
//
|
661
|
+
//
|
662
|
+
function normalizeConnectArgs(listArgs) {
|
663
|
+
var args = net._normalizeConnectArgs(listArgs);
|
664
|
+
var options = args[0];
|
665
|
+
var cb = args[1];
|
666
|
+
|
667
|
+
if (util.isObject(listArgs[1])) {
|
668
|
+
options = util._extend(options, listArgs[1]);
|
669
|
+
} else if (util.isObject(listArgs[2])) {
|
670
|
+
options = util._extend(options, listArgs[2]);
|
671
|
+
}
|
672
|
+
|
673
|
+
return (cb) ? [options, cb] : [options];
|
674
|
+
}
|
675
|
+
|
676
|
+
function legacyConnect(hostname, options, NPN, credentials) {
|
677
|
+
assert(options.socket);
|
678
|
+
if (!tls_legacy)
|
679
|
+
tls_legacy = require('_tls_legacy');
|
680
|
+
|
681
|
+
var pair = tls_legacy.createSecurePair(credentials,
|
682
|
+
false,
|
683
|
+
true,
|
684
|
+
!!options.rejectUnauthorized,
|
685
|
+
{
|
686
|
+
NPNProtocols: NPN.NPNProtocols,
|
687
|
+
servername: hostname
|
688
|
+
});
|
689
|
+
tls_legacy.pipe(pair, options.socket);
|
690
|
+
pair.cleartext._controlReleased = true;
|
691
|
+
pair.on('error', function(err) {
|
692
|
+
pair.cleartext.emit('error', err);
|
693
|
+
});
|
694
|
+
|
695
|
+
return pair;
|
696
|
+
}
|
697
|
+
|
698
|
+
exports.connect = function(/* [port, host], options, cb */) {
|
699
|
+
var args = normalizeConnectArgs(arguments);
|
700
|
+
var options = args[0];
|
701
|
+
var cb = args[1];
|
702
|
+
|
703
|
+
var defaults = {
|
704
|
+
rejectUnauthorized: '0' !== process.env.NODE_TLS_REJECT_UNAUTHORIZED
|
705
|
+
};
|
706
|
+
options = util._extend(defaults, options || {});
|
707
|
+
|
708
|
+
var hostname = options.servername ||
|
709
|
+
options.host ||
|
710
|
+
options.socket && options.socket._host,
|
711
|
+
NPN = {},
|
712
|
+
credentials = crypto.createCredentials(options);
|
713
|
+
tls.convertNPNProtocols(options.NPNProtocols, NPN);
|
714
|
+
|
715
|
+
// Wrapping TLS socket inside another TLS socket was requested -
|
716
|
+
// create legacy secure pair
|
717
|
+
var socket;
|
718
|
+
var legacy;
|
719
|
+
var result;
|
720
|
+
if (options.socket instanceof TLSSocket) {
|
721
|
+
debug('legacy connect');
|
722
|
+
legacy = true;
|
723
|
+
socket = legacyConnect(hostname, options, NPN, credentials);
|
724
|
+
result = socket.cleartext;
|
725
|
+
} else {
|
726
|
+
legacy = false;
|
727
|
+
socket = new TLSSocket(options.socket, {
|
728
|
+
credentials: credentials,
|
729
|
+
isServer: false,
|
730
|
+
requestCert: true,
|
731
|
+
rejectUnauthorized: options.rejectUnauthorized,
|
732
|
+
NPNProtocols: NPN.NPNProtocols
|
733
|
+
});
|
734
|
+
result = socket;
|
735
|
+
}
|
736
|
+
|
737
|
+
if (socket._handle && !socket._connecting) {
|
738
|
+
onHandle();
|
739
|
+
} else {
|
740
|
+
// Not even started connecting yet (or probably resolving dns address),
|
741
|
+
// catch socket errors and assign handle.
|
742
|
+
if (!legacy && options.socket) {
|
743
|
+
options.socket.once('connect', function() {
|
744
|
+
assert(options.socket._handle);
|
745
|
+
socket._handle = options.socket._handle;
|
746
|
+
socket._handle.owner = socket;
|
747
|
+
socket.emit('connect');
|
748
|
+
});
|
749
|
+
}
|
750
|
+
socket.once('connect', onHandle);
|
751
|
+
}
|
752
|
+
|
753
|
+
if (cb)
|
754
|
+
result.once('secureConnect', cb);
|
755
|
+
|
756
|
+
if (!options.socket) {
|
757
|
+
assert(!legacy);
|
758
|
+
var connect_opt;
|
759
|
+
if (options.path && !options.port) {
|
760
|
+
connect_opt = { path: options.path };
|
761
|
+
} else {
|
762
|
+
connect_opt = {
|
763
|
+
port: options.port,
|
764
|
+
host: options.host,
|
765
|
+
localAddress: options.localAddress
|
766
|
+
};
|
767
|
+
}
|
768
|
+
socket.connect(connect_opt);
|
769
|
+
}
|
770
|
+
|
771
|
+
return result;
|
772
|
+
|
773
|
+
function onHandle() {
|
774
|
+
if (!legacy)
|
775
|
+
socket._releaseControl();
|
776
|
+
|
777
|
+
if (options.session)
|
778
|
+
socket.setSession(options.session);
|
779
|
+
|
780
|
+
if (!legacy) {
|
781
|
+
if (options.servername)
|
782
|
+
socket.setServername(options.servername);
|
783
|
+
|
784
|
+
socket._start();
|
785
|
+
}
|
786
|
+
socket.on('secure', function() {
|
787
|
+
var verifyError = socket.ssl.verifyError();
|
788
|
+
|
789
|
+
// Verify that server's identity matches it's certificate's names
|
790
|
+
if (!verifyError) {
|
791
|
+
var cert = result.getPeerCertificate();
|
792
|
+
var validCert = tls.checkServerIdentity(hostname, cert);
|
793
|
+
if (!validCert) {
|
794
|
+
verifyError = new Error('Hostname/IP doesn\'t match certificate\'s ' +
|
795
|
+
'altnames');
|
796
|
+
}
|
797
|
+
}
|
798
|
+
|
799
|
+
if (verifyError) {
|
800
|
+
result.authorized = false;
|
801
|
+
result.authorizationError = verifyError.code || verifyError.message;
|
802
|
+
|
803
|
+
if (options.rejectUnauthorized) {
|
804
|
+
result.emit('error', verifyError);
|
805
|
+
result.destroy();
|
806
|
+
return;
|
807
|
+
} else {
|
808
|
+
result.emit('secureConnect');
|
809
|
+
}
|
810
|
+
} else {
|
811
|
+
result.authorized = true;
|
812
|
+
result.emit('secureConnect');
|
813
|
+
}
|
814
|
+
|
815
|
+
// Uncork incoming data
|
816
|
+
result.removeListener('end', onHangUp);
|
817
|
+
});
|
818
|
+
|
819
|
+
function onHangUp() {
|
820
|
+
// NOTE: This logic is shared with _http_client.js
|
821
|
+
if (!socket._hadError) {
|
822
|
+
socket._hadError = true;
|
823
|
+
var error = new Error('socket hang up');
|
824
|
+
error.code = 'ECONNRESET';
|
825
|
+
socket.destroy();
|
826
|
+
socket.emit('error', error);
|
827
|
+
}
|
828
|
+
}
|
829
|
+
result.once('end', onHangUp);
|
830
|
+
}
|
831
|
+
};
|