socket.js 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792
  1. /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
  2. /* This Source Code Form is subject to the terms of the Mozilla Public
  3. * License, v. 2.0. If a copy of the MPL was not distributed with this
  4. * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
  5. "use strict";
  6. var { Ci, Cc, CC, Cr, Cu } = require("chrome");
  7. // Ensure PSM is initialized to support TLS sockets
  8. Cc["@mozilla.org/psm;1"].getService(Ci.nsISupports);
  9. var Services = require("Services");
  10. var promise = require("promise");
  11. var defer = require("devtools/shared/defer");
  12. var DevToolsUtils = require("devtools/shared/DevToolsUtils");
  13. var { dumpn, dumpv } = DevToolsUtils;
  14. loader.lazyRequireGetter(this, "WebSocketServer",
  15. "devtools/server/websocket-server");
  16. loader.lazyRequireGetter(this, "DebuggerTransport",
  17. "devtools/shared/transport/transport", true);
  18. loader.lazyRequireGetter(this, "WebSocketDebuggerTransport",
  19. "devtools/shared/transport/websocket-transport");
  20. loader.lazyRequireGetter(this, "DebuggerServer",
  21. "devtools/server/main", true);
  22. loader.lazyRequireGetter(this, "discovery",
  23. "devtools/shared/discovery/discovery");
  24. loader.lazyRequireGetter(this, "cert",
  25. "devtools/shared/security/cert");
  26. loader.lazyRequireGetter(this, "Authenticators",
  27. "devtools/shared/security/auth", true);
  28. loader.lazyRequireGetter(this, "AuthenticationResult",
  29. "devtools/shared/security/auth", true);
  30. DevToolsUtils.defineLazyGetter(this, "nsFile", () => {
  31. return CC("@mozilla.org/file/local;1", "nsIFile", "initWithPath");
  32. });
  33. DevToolsUtils.defineLazyGetter(this, "socketTransportService", () => {
  34. return Cc["@mozilla.org/network/socket-transport-service;1"]
  35. .getService(Ci.nsISocketTransportService);
  36. });
  37. DevToolsUtils.defineLazyGetter(this, "certOverrideService", () => {
  38. return Cc["@mozilla.org/security/certoverride;1"]
  39. .getService(Ci.nsICertOverrideService);
  40. });
  41. DevToolsUtils.defineLazyGetter(this, "nssErrorsService", () => {
  42. return Cc["@mozilla.org/nss_errors_service;1"]
  43. .getService(Ci.nsINSSErrorsService);
  44. });
  45. const { Task } = require("devtools/shared/task");
  46. var DebuggerSocket = {};
  47. /**
  48. * Connects to a debugger server socket.
  49. *
  50. * @param host string
  51. * The host name or IP address of the debugger server.
  52. * @param port number
  53. * The port number of the debugger server.
  54. * @param encryption boolean (optional)
  55. * Whether the server requires encryption. Defaults to false.
  56. * @param webSocket boolean (optional)
  57. * Whether to use WebSocket protocol to connect. Defaults to false.
  58. * @param authenticator Authenticator (optional)
  59. * |Authenticator| instance matching the mode in use by the server.
  60. * Defaults to a PROMPT instance if not supplied.
  61. * @param cert object (optional)
  62. * The server's cert details. Used with OOB_CERT authentication.
  63. * @return promise
  64. * Resolved to a DebuggerTransport instance.
  65. */
  66. DebuggerSocket.connect = Task.async(function* (settings) {
  67. // Default to PROMPT |Authenticator| instance if not supplied
  68. if (!settings.authenticator) {
  69. settings.authenticator = new (Authenticators.get().Client)();
  70. }
  71. _validateSettings(settings);
  72. let { host, port, encryption, authenticator, cert } = settings;
  73. let transport = yield _getTransport(settings);
  74. yield authenticator.authenticate({
  75. host,
  76. port,
  77. encryption,
  78. cert,
  79. transport
  80. });
  81. transport.connectionSettings = settings;
  82. return transport;
  83. });
  84. /**
  85. * Validate that the connection settings have been set to a supported configuration.
  86. */
  87. function _validateSettings(settings) {
  88. let { encryption, webSocket, authenticator } = settings;
  89. if (webSocket && encryption) {
  90. throw new Error("Encryption not supported on WebSocket transport");
  91. }
  92. authenticator.validateSettings(settings);
  93. }
  94. /**
  95. * Try very hard to create a DevTools transport, potentially making several
  96. * connect attempts in the process.
  97. *
  98. * @param host string
  99. * The host name or IP address of the debugger server.
  100. * @param port number
  101. * The port number of the debugger server.
  102. * @param encryption boolean (optional)
  103. * Whether the server requires encryption. Defaults to false.
  104. * @param webSocket boolean (optional)
  105. * Whether to use WebSocket protocol to connect to the server. Defaults to false.
  106. * @param authenticator Authenticator
  107. * |Authenticator| instance matching the mode in use by the server.
  108. * Defaults to a PROMPT instance if not supplied.
  109. * @param cert object (optional)
  110. * The server's cert details. Used with OOB_CERT authentication.
  111. * @return transport DebuggerTransport
  112. * A possible DevTools transport (if connection succeeded and streams
  113. * are actually alive and working)
  114. */
  115. var _getTransport = Task.async(function* (settings) {
  116. let { host, port, encryption, webSocket } = settings;
  117. if (webSocket) {
  118. // Establish a connection and wait until the WebSocket is ready to send and receive
  119. let socket = yield new Promise((resolve, reject) => {
  120. let s = new WebSocket(`ws://${host}:${port}`);
  121. s.onopen = () => resolve(s);
  122. s.onerror = err => reject(err);
  123. });
  124. return new WebSocketDebuggerTransport(socket);
  125. }
  126. let attempt = yield _attemptTransport(settings);
  127. if (attempt.transport) {
  128. return attempt.transport; // Success
  129. }
  130. // If the server cert failed validation, store a temporary override and make
  131. // a second attempt.
  132. if (encryption && attempt.certError) {
  133. _storeCertOverride(attempt.s, host, port);
  134. } else {
  135. throw new Error("Connection failed");
  136. }
  137. attempt = yield _attemptTransport(settings);
  138. if (attempt.transport) {
  139. return attempt.transport; // Success
  140. }
  141. throw new Error("Connection failed even after cert override");
  142. });
  143. /**
  144. * Make a single attempt to connect and create a DevTools transport. This could
  145. * fail if the remote host is unreachable, for example. If there is security
  146. * error due to the use of self-signed certs, you should make another attempt
  147. * after storing a cert override.
  148. *
  149. * @param host string
  150. * The host name or IP address of the debugger server.
  151. * @param port number
  152. * The port number of the debugger server.
  153. * @param encryption boolean (optional)
  154. * Whether the server requires encryption. Defaults to false.
  155. * @param authenticator Authenticator
  156. * |Authenticator| instance matching the mode in use by the server.
  157. * Defaults to a PROMPT instance if not supplied.
  158. * @param cert object (optional)
  159. * The server's cert details. Used with OOB_CERT authentication.
  160. * @return transport DebuggerTransport
  161. * A possible DevTools transport (if connection succeeded and streams
  162. * are actually alive and working)
  163. * @return certError boolean
  164. * Flag noting if cert trouble caused the streams to fail
  165. * @return s nsISocketTransport
  166. * Underlying socket transport, in case more details are needed.
  167. */
  168. var _attemptTransport = Task.async(function* (settings) {
  169. let { authenticator } = settings;
  170. // _attemptConnect only opens the streams. Any failures at that stage
  171. // aborts the connection process immedidately.
  172. let { s, input, output } = yield _attemptConnect(settings);
  173. // Check if the input stream is alive. If encryption is enabled, we need to
  174. // watch out for cert errors by testing the input stream.
  175. let alive, certError;
  176. try {
  177. let results = yield _isInputAlive(input);
  178. alive = results.alive;
  179. certError = results.certError;
  180. } catch (e) {
  181. // For other unexpected errors, like NS_ERROR_CONNECTION_REFUSED, we reach
  182. // this block.
  183. input.close();
  184. output.close();
  185. throw e;
  186. }
  187. dumpv("Server cert accepted? " + !certError);
  188. // The |Authenticator| examines the connection as well and may determine it
  189. // should be dropped.
  190. alive = alive && authenticator.validateConnection({
  191. host: settings.host,
  192. port: settings.port,
  193. encryption: settings.encryption,
  194. cert: settings.cert,
  195. socket: s
  196. });
  197. let transport;
  198. if (alive) {
  199. transport = new DebuggerTransport(input, output);
  200. } else {
  201. // Something went wrong, close the streams.
  202. input.close();
  203. output.close();
  204. }
  205. return { transport, certError, s };
  206. });
  207. /**
  208. * Try to connect to a remote server socket.
  209. *
  210. * If successsful, the socket transport and its opened streams are returned.
  211. * Typically, this will only fail if the host / port is unreachable. Other
  212. * problems, such as security errors, will allow this stage to succeed, but then
  213. * fail later when the streams are actually used.
  214. * @return s nsISocketTransport
  215. * Underlying socket transport, in case more details are needed.
  216. * @return input nsIAsyncInputStream
  217. * The socket's input stream.
  218. * @return output nsIAsyncOutputStream
  219. * The socket's output stream.
  220. */
  221. var _attemptConnect = Task.async(function* ({ host, port, encryption }) {
  222. let s;
  223. if (encryption) {
  224. s = socketTransportService.createTransport(["ssl"], 1, host, port, null);
  225. } else {
  226. s = socketTransportService.createTransport(null, 0, host, port, null);
  227. }
  228. // By default the CONNECT socket timeout is very long, 65535 seconds,
  229. // so that if we race to be in CONNECT state while the server socket is still
  230. // initializing, the connection is stuck in connecting state for 18.20 hours!
  231. s.setTimeout(Ci.nsISocketTransport.TIMEOUT_CONNECT, 2);
  232. // If encrypting, load the client cert now, so we can deliver it at just the
  233. // right time.
  234. let clientCert;
  235. if (encryption) {
  236. clientCert = yield cert.local.getOrCreate();
  237. }
  238. let deferred = defer();
  239. let input;
  240. let output;
  241. // Delay opening the input stream until the transport has fully connected.
  242. // The goal is to avoid showing the user a client cert UI prompt when
  243. // encryption is used. This prompt is shown when the client opens the input
  244. // stream and does not know which client cert to present to the server. To
  245. // specify a client cert programmatically, we need to access the transport's
  246. // nsISSLSocketControl interface, which is not accessible until the transport
  247. // has connected.
  248. s.setEventSink({
  249. onTransportStatus(transport, status) {
  250. if (status != Ci.nsISocketTransport.STATUS_CONNECTING_TO) {
  251. return;
  252. }
  253. if (encryption) {
  254. let sslSocketControl =
  255. transport.securityInfo.QueryInterface(Ci.nsISSLSocketControl);
  256. sslSocketControl.clientCert = clientCert;
  257. }
  258. try {
  259. input = s.openInputStream(0, 0, 0);
  260. } catch (e) {
  261. deferred.reject(e);
  262. }
  263. deferred.resolve({ s, input, output });
  264. }
  265. }, Services.tm.currentThread);
  266. // openOutputStream may throw NS_ERROR_NOT_INITIALIZED if we hit some race
  267. // where the nsISocketTransport gets shutdown in between its instantiation and
  268. // the call to this method.
  269. try {
  270. output = s.openOutputStream(0, 0, 0);
  271. } catch (e) {
  272. deferred.reject(e);
  273. }
  274. deferred.promise.catch(e => {
  275. if (input) {
  276. input.close();
  277. }
  278. if (output) {
  279. output.close();
  280. }
  281. DevToolsUtils.reportException("_attemptConnect", e);
  282. });
  283. return deferred.promise;
  284. });
  285. /**
  286. * Check if the input stream is alive. For an encrypted connection, it may not
  287. * be if the client refuses the server's cert. A cert error is expected on
  288. * first connection to a new host because the cert is self-signed.
  289. */
  290. function _isInputAlive(input) {
  291. let deferred = defer();
  292. input.asyncWait({
  293. onInputStreamReady(stream) {
  294. try {
  295. stream.available();
  296. deferred.resolve({ alive: true });
  297. } catch (e) {
  298. try {
  299. // getErrorClass may throw if you pass a non-NSS error
  300. let errorClass = nssErrorsService.getErrorClass(e.result);
  301. if (errorClass === Ci.nsINSSErrorsService.ERROR_CLASS_BAD_CERT) {
  302. deferred.resolve({ certError: true });
  303. } else {
  304. deferred.reject(e);
  305. }
  306. } catch (nssErr) {
  307. deferred.reject(e);
  308. }
  309. }
  310. }
  311. }, 0, 0, Services.tm.currentThread);
  312. return deferred.promise;
  313. }
  314. /**
  315. * To allow the connection to proceed with self-signed cert, we store a cert
  316. * override. This implies that we take on the burden of authentication for
  317. * these connections.
  318. */
  319. function _storeCertOverride(s, host, port) {
  320. let cert = s.securityInfo.QueryInterface(Ci.nsISSLStatusProvider)
  321. .SSLStatus.serverCert;
  322. let overrideBits = Ci.nsICertOverrideService.ERROR_UNTRUSTED |
  323. Ci.nsICertOverrideService.ERROR_MISMATCH;
  324. certOverrideService.rememberValidityOverride(host, port, cert, overrideBits,
  325. true /* temporary */);
  326. }
  327. /**
  328. * Creates a new socket listener for remote connections to the DebuggerServer.
  329. * This helps contain and organize the parts of the server that may differ or
  330. * are particular to one given listener mechanism vs. another.
  331. */
  332. function SocketListener() {}
  333. SocketListener.prototype = {
  334. /* Socket Options */
  335. /**
  336. * The port or path to listen on.
  337. *
  338. * If given an integer, the port to listen on. Use -1 to choose any available
  339. * port. Otherwise, the path to the unix socket domain file to listen on.
  340. */
  341. portOrPath: null,
  342. /**
  343. * Controls whether this listener is announced via the service discovery
  344. * mechanism.
  345. */
  346. discoverable: false,
  347. /**
  348. * Controls whether this listener's transport uses encryption.
  349. */
  350. encryption: false,
  351. /**
  352. * Controls the |Authenticator| used, which hooks various socket steps to
  353. * implement an authentication policy. It is expected that different use
  354. * cases may override pieces of the |Authenticator|. See auth.js.
  355. *
  356. * Here we set the default |Authenticator|, which is |Prompt|.
  357. */
  358. authenticator: new (Authenticators.get().Server)(),
  359. /**
  360. * Validate that all options have been set to a supported configuration.
  361. */
  362. _validateOptions: function () {
  363. if (this.portOrPath === null) {
  364. throw new Error("Must set a port / path to listen on.");
  365. }
  366. if (this.discoverable && !Number(this.portOrPath)) {
  367. throw new Error("Discovery only supported for TCP sockets.");
  368. }
  369. if (this.encryption && this.webSocket) {
  370. throw new Error("Encryption not supported on WebSocket transport");
  371. }
  372. this.authenticator.validateOptions(this);
  373. },
  374. /**
  375. * Listens on the given port or socket file for remote debugger connections.
  376. */
  377. open: function () {
  378. this._validateOptions();
  379. DebuggerServer._addListener(this);
  380. let flags = Ci.nsIServerSocket.KeepWhenOffline;
  381. // A preference setting can force binding on the loopback interface.
  382. if (Services.prefs.getBoolPref("devtools.debugger.force-local")) {
  383. flags |= Ci.nsIServerSocket.LoopbackOnly;
  384. }
  385. let self = this;
  386. return Task.spawn(function* () {
  387. let backlog = 4;
  388. self._socket = self._createSocketInstance();
  389. if (self.isPortBased) {
  390. let port = Number(self.portOrPath);
  391. self._socket.initSpecialConnection(port, flags, backlog);
  392. } else {
  393. let file = nsFile(self.portOrPath);
  394. if (file.exists()) {
  395. file.remove(false);
  396. }
  397. self._socket.initWithFilename(file, parseInt("666", 8), backlog);
  398. }
  399. yield self._setAdditionalSocketOptions();
  400. self._socket.asyncListen(self);
  401. dumpn("Socket listening on: " + (self.port || self.portOrPath));
  402. }).then(() => {
  403. this._advertise();
  404. }).catch(e => {
  405. dumpn("Could not start debugging listener on '" + this.portOrPath +
  406. "': " + e);
  407. this.close();
  408. });
  409. },
  410. _advertise: function () {
  411. if (!this.discoverable || !this.port) {
  412. return;
  413. }
  414. let advertisement = {
  415. port: this.port,
  416. encryption: this.encryption,
  417. };
  418. this.authenticator.augmentAdvertisement(this, advertisement);
  419. discovery.addService("devtools", advertisement);
  420. },
  421. _createSocketInstance: function () {
  422. if (this.encryption) {
  423. return Cc["@mozilla.org/network/tls-server-socket;1"]
  424. .createInstance(Ci.nsITLSServerSocket);
  425. }
  426. return Cc["@mozilla.org/network/server-socket;1"]
  427. .createInstance(Ci.nsIServerSocket);
  428. },
  429. _setAdditionalSocketOptions: Task.async(function* () {
  430. if (this.encryption) {
  431. this._socket.serverCert = yield cert.local.getOrCreate();
  432. this._socket.setSessionTickets(false);
  433. let requestCert = Ci.nsITLSServerSocket.REQUEST_NEVER;
  434. this._socket.setRequestClientCertificate(requestCert);
  435. }
  436. this.authenticator.augmentSocketOptions(this, this._socket);
  437. }),
  438. /**
  439. * Closes the SocketListener. Notifies the server to remove the listener from
  440. * the set of active SocketListeners.
  441. */
  442. close: function () {
  443. if (this.discoverable && this.port) {
  444. discovery.removeService("devtools");
  445. }
  446. if (this._socket) {
  447. this._socket.close();
  448. this._socket = null;
  449. }
  450. DebuggerServer._removeListener(this);
  451. },
  452. get host() {
  453. if (!this._socket) {
  454. return null;
  455. }
  456. if (Services.prefs.getBoolPref("devtools.debugger.force-local")) {
  457. return "127.0.0.1";
  458. }
  459. return "0.0.0.0";
  460. },
  461. /**
  462. * Gets whether this listener uses a port number vs. a path.
  463. */
  464. get isPortBased() {
  465. return !!Number(this.portOrPath);
  466. },
  467. /**
  468. * Gets the port that a TCP socket listener is listening on, or null if this
  469. * is not a TCP socket (so there is no port).
  470. */
  471. get port() {
  472. if (!this.isPortBased || !this._socket) {
  473. return null;
  474. }
  475. return this._socket.port;
  476. },
  477. get cert() {
  478. if (!this._socket || !this._socket.serverCert) {
  479. return null;
  480. }
  481. return {
  482. sha256: this._socket.serverCert.sha256Fingerprint
  483. };
  484. },
  485. // nsIServerSocketListener implementation
  486. onSocketAccepted:
  487. DevToolsUtils.makeInfallible(function (socket, socketTransport) {
  488. new ServerSocketConnection(this, socketTransport);
  489. }, "SocketListener.onSocketAccepted"),
  490. onStopListening: function (socket, status) {
  491. dumpn("onStopListening, status: " + status);
  492. }
  493. };
  494. // Client must complete TLS handshake within this window (ms)
  495. loader.lazyGetter(this, "HANDSHAKE_TIMEOUT", () => {
  496. return Services.prefs.getIntPref("devtools.remote.tls-handshake-timeout");
  497. });
  498. /**
  499. * A |ServerSocketConnection| is created by a |SocketListener| for each accepted
  500. * incoming socket. This is a short-lived object used to implement
  501. * authentication and verify encryption prior to handing off the connection to
  502. * the |DebuggerServer|.
  503. */
  504. function ServerSocketConnection(listener, socketTransport) {
  505. this._listener = listener;
  506. this._socketTransport = socketTransport;
  507. this._handle();
  508. }
  509. ServerSocketConnection.prototype = {
  510. get authentication() {
  511. return this._listener.authenticator.mode;
  512. },
  513. get host() {
  514. return this._socketTransport.host;
  515. },
  516. get port() {
  517. return this._socketTransport.port;
  518. },
  519. get cert() {
  520. if (!this._clientCert) {
  521. return null;
  522. }
  523. return {
  524. sha256: this._clientCert.sha256Fingerprint
  525. };
  526. },
  527. get address() {
  528. return this.host + ":" + this.port;
  529. },
  530. get client() {
  531. let client = {
  532. host: this.host,
  533. port: this.port
  534. };
  535. if (this.cert) {
  536. client.cert = this.cert;
  537. }
  538. return client;
  539. },
  540. get server() {
  541. let server = {
  542. host: this._listener.host,
  543. port: this._listener.port
  544. };
  545. if (this._listener.cert) {
  546. server.cert = this._listener.cert;
  547. }
  548. return server;
  549. },
  550. /**
  551. * This is the main authentication workflow. If any pieces reject a promise,
  552. * the connection is denied. If the entire process resolves successfully,
  553. * the connection is finally handed off to the |DebuggerServer|.
  554. */
  555. _handle() {
  556. dumpn("Debugging connection starting authentication on " + this.address);
  557. let self = this;
  558. Task.spawn(function* () {
  559. self._listenForTLSHandshake();
  560. yield self._createTransport();
  561. yield self._awaitTLSHandshake();
  562. yield self._authenticate();
  563. }).then(() => this.allow()).catch(e => this.deny(e));
  564. },
  565. /**
  566. * We need to open the streams early on, as that is required in the case of
  567. * TLS sockets to keep the handshake moving.
  568. */
  569. _createTransport: Task.async(function* () {
  570. let input = this._socketTransport.openInputStream(0, 0, 0);
  571. let output = this._socketTransport.openOutputStream(0, 0, 0);
  572. if (this._listener.webSocket) {
  573. let socket = yield WebSocketServer.accept(this._socketTransport, input, output);
  574. this._transport = new WebSocketDebuggerTransport(socket);
  575. } else {
  576. this._transport = new DebuggerTransport(input, output);
  577. }
  578. // Start up the transport to observe the streams in case they are closed
  579. // early. This allows us to clean up our state as well.
  580. this._transport.hooks = {
  581. onClosed: reason => {
  582. this.deny(reason);
  583. }
  584. };
  585. this._transport.ready();
  586. }),
  587. /**
  588. * Set the socket's security observer, which receives an event via the
  589. * |onHandshakeDone| callback when the TLS handshake completes.
  590. */
  591. _setSecurityObserver(observer) {
  592. if (!this._socketTransport || !this._socketTransport.securityInfo) {
  593. return;
  594. }
  595. let connectionInfo = this._socketTransport.securityInfo
  596. .QueryInterface(Ci.nsITLSServerConnectionInfo);
  597. connectionInfo.setSecurityObserver(observer);
  598. },
  599. /**
  600. * When encryption is used, we wait for the client to complete the TLS
  601. * handshake before proceeding. The handshake details are validated in
  602. * |onHandshakeDone|.
  603. */
  604. _listenForTLSHandshake() {
  605. this._handshakeDeferred = defer();
  606. if (!this._listener.encryption) {
  607. this._handshakeDeferred.resolve();
  608. return;
  609. }
  610. this._setSecurityObserver(this);
  611. this._handshakeTimeout = setTimeout(this._onHandshakeTimeout.bind(this),
  612. HANDSHAKE_TIMEOUT);
  613. },
  614. _awaitTLSHandshake() {
  615. return this._handshakeDeferred.promise;
  616. },
  617. _onHandshakeTimeout() {
  618. dumpv("Client failed to complete TLS handshake");
  619. this._handshakeDeferred.reject(Cr.NS_ERROR_NET_TIMEOUT);
  620. },
  621. // nsITLSServerSecurityObserver implementation
  622. onHandshakeDone(socket, clientStatus) {
  623. clearTimeout(this._handshakeTimeout);
  624. this._setSecurityObserver(null);
  625. dumpv("TLS version: " + clientStatus.tlsVersionUsed.toString(16));
  626. dumpv("TLS cipher: " + clientStatus.cipherName);
  627. dumpv("TLS key length: " + clientStatus.keyLength);
  628. dumpv("TLS MAC length: " + clientStatus.macLength);
  629. this._clientCert = clientStatus.peerCert;
  630. /*
  631. * TODO: These rules should be really be set on the TLS socket directly, but
  632. * this would need more platform work to expose it via XPCOM.
  633. *
  634. * Enforcing cipher suites here would be a bad idea, as we want TLS
  635. * cipher negotiation to work correctly. The server already allows only
  636. * Gecko's normal set of cipher suites.
  637. */
  638. if (clientStatus.tlsVersionUsed < Ci.nsITLSClientStatus.TLS_VERSION_1_2) {
  639. this._handshakeDeferred.reject(Cr.NS_ERROR_CONNECTION_REFUSED);
  640. return;
  641. }
  642. this._handshakeDeferred.resolve();
  643. },
  644. _authenticate: Task.async(function* () {
  645. let result = yield this._listener.authenticator.authenticate({
  646. client: this.client,
  647. server: this.server,
  648. transport: this._transport
  649. });
  650. switch (result) {
  651. case AuthenticationResult.DISABLE_ALL:
  652. DebuggerServer.closeAllListeners();
  653. Services.prefs.setBoolPref("devtools.debugger.remote-enabled", false);
  654. return promise.reject(Cr.NS_ERROR_CONNECTION_REFUSED);
  655. case AuthenticationResult.DENY:
  656. return promise.reject(Cr.NS_ERROR_CONNECTION_REFUSED);
  657. case AuthenticationResult.ALLOW:
  658. case AuthenticationResult.ALLOW_PERSIST:
  659. return promise.resolve();
  660. default:
  661. return promise.reject(Cr.NS_ERROR_CONNECTION_REFUSED);
  662. }
  663. }),
  664. deny(result) {
  665. if (this._destroyed) {
  666. return;
  667. }
  668. let errorName = result;
  669. for (let name in Cr) {
  670. if (Cr[name] === result) {
  671. errorName = name;
  672. break;
  673. }
  674. }
  675. dumpn("Debugging connection denied on " + this.address +
  676. " (" + errorName + ")");
  677. if (this._transport) {
  678. this._transport.hooks = null;
  679. this._transport.close(result);
  680. }
  681. this._socketTransport.close(result);
  682. this.destroy();
  683. },
  684. allow() {
  685. if (this._destroyed) {
  686. return;
  687. }
  688. dumpn("Debugging connection allowed on " + this.address);
  689. DebuggerServer._onConnection(this._transport);
  690. this.destroy();
  691. },
  692. destroy() {
  693. this._destroyed = true;
  694. clearTimeout(this._handshakeTimeout);
  695. this._setSecurityObserver(null);
  696. this._listener = null;
  697. this._socketTransport = null;
  698. this._transport = null;
  699. this._clientCert = null;
  700. }
  701. };
  702. DebuggerSocket.createListener = function () {
  703. return new SocketListener();
  704. };
  705. exports.DebuggerSocket = DebuggerSocket;