Bug 1622408 [wpt PR 22244] - Restore the event delegate for a CSSTransition after...
[gecko.git] / devtools / shared / security / socket.js
blob08d0fe2d1219664a4bddd8112fc29312ff9eb517
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this
3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 "use strict";
7 var { Ci, Cc, CC, Cr } = require("chrome");
9 // Ensure PSM is initialized to support TLS sockets
10 Cc["@mozilla.org/psm;1"].getService(Ci.nsISupports);
12 var Services = require("Services");
13 var promise = require("promise");
14 var defer = require("devtools/shared/defer");
15 var DevToolsUtils = require("devtools/shared/DevToolsUtils");
16 var { dumpn, dumpv } = DevToolsUtils;
17 loader.lazyRequireGetter(
18   this,
19   "WebSocketServer",
20   "devtools/server/socket/websocket-server"
22 loader.lazyRequireGetter(
23   this,
24   "DebuggerTransport",
25   "devtools/shared/transport/transport",
26   true
28 loader.lazyRequireGetter(
29   this,
30   "WebSocketDebuggerTransport",
31   "devtools/shared/transport/websocket-transport"
33 loader.lazyRequireGetter(
34   this,
35   "discovery",
36   "devtools/shared/discovery/discovery"
38 loader.lazyRequireGetter(this, "cert", "devtools/shared/security/cert");
39 loader.lazyRequireGetter(
40   this,
41   "Authenticators",
42   "devtools/shared/security/auth",
43   true
45 loader.lazyRequireGetter(
46   this,
47   "AuthenticationResult",
48   "devtools/shared/security/auth",
49   true
51 loader.lazyRequireGetter(this, "EventEmitter", "devtools/shared/event-emitter");
53 DevToolsUtils.defineLazyGetter(this, "nsFile", () => {
54   return CC("@mozilla.org/file/local;1", "nsIFile", "initWithPath");
55 });
57 DevToolsUtils.defineLazyGetter(this, "socketTransportService", () => {
58   return Cc["@mozilla.org/network/socket-transport-service;1"].getService(
59     Ci.nsISocketTransportService
60   );
61 });
63 DevToolsUtils.defineLazyGetter(this, "certOverrideService", () => {
64   return Cc["@mozilla.org/security/certoverride;1"].getService(
65     Ci.nsICertOverrideService
66   );
67 });
69 DevToolsUtils.defineLazyGetter(this, "nssErrorsService", () => {
70   return Cc["@mozilla.org/nss_errors_service;1"].getService(
71     Ci.nsINSSErrorsService
72   );
73 });
75 var DebuggerSocket = {};
77 /**
78  * Connects to a devtools server socket.
79  *
80  * @param host string
81  *        The host name or IP address of the devtools server.
82  * @param port number
83  *        The port number of the devtools server.
84  * @param encryption boolean (optional)
85  *        Whether the server requires encryption.  Defaults to false.
86  * @param webSocket boolean (optional)
87  *        Whether to use WebSocket protocol to connect. Defaults to false.
88  * @param authenticator Authenticator (optional)
89  *        |Authenticator| instance matching the mode in use by the server.
90  *        Defaults to a PROMPT instance if not supplied.
91  * @param cert object (optional)
92  *        The server's cert details.  Used with OOB_CERT authentication.
93  * @return promise
94  *         Resolved to a DebuggerTransport instance.
95  */
96 DebuggerSocket.connect = async function(settings) {
97   // Default to PROMPT |Authenticator| instance if not supplied
98   if (!settings.authenticator) {
99     settings.authenticator = new (Authenticators.get().Client)();
100   }
101   _validateSettings(settings);
102   // eslint-disable-next-line no-shadow
103   const { host, port, encryption, authenticator, cert } = settings;
104   const transport = await _getTransport(settings);
105   await authenticator.authenticate({
106     host,
107     port,
108     encryption,
109     cert,
110     transport,
111   });
112   transport.connectionSettings = settings;
113   return transport;
117  * Validate that the connection settings have been set to a supported configuration.
118  */
119 function _validateSettings(settings) {
120   const { encryption, webSocket, authenticator } = settings;
122   if (webSocket && encryption) {
123     throw new Error("Encryption not supported on WebSocket transport");
124   }
125   authenticator.validateSettings(settings);
129  * Try very hard to create a DevTools transport, potentially making several
130  * connect attempts in the process.
132  * @param host string
133  *        The host name or IP address of the devtools server.
134  * @param port number
135  *        The port number of the devtools server.
136  * @param encryption boolean (optional)
137  *        Whether the server requires encryption.  Defaults to false.
138  * @param webSocket boolean (optional)
139  *        Whether to use WebSocket protocol to connect to the server. Defaults to false.
140  * @param authenticator Authenticator
141  *        |Authenticator| instance matching the mode in use by the server.
142  *        Defaults to a PROMPT instance if not supplied.
143  * @param cert object (optional)
144  *        The server's cert details.  Used with OOB_CERT authentication.
145  * @return transport DebuggerTransport
146  *         A possible DevTools transport (if connection succeeded and streams
147  *         are actually alive and working)
148  */
149 var _getTransport = async function(settings) {
150   const { host, port, encryption, webSocket } = settings;
152   if (webSocket) {
153     // Establish a connection and wait until the WebSocket is ready to send and receive
154     const socket = await new Promise((resolve, reject) => {
155       const s = new WebSocket(`ws://${host}:${port}`);
156       s.onopen = () => resolve(s);
157       s.onerror = err => reject(err);
158     });
160     return new WebSocketDebuggerTransport(socket);
161   }
163   let attempt = await _attemptTransport(settings);
164   if (attempt.transport) {
165     // Success
166     return attempt.transport;
167   }
169   // If the server cert failed validation, store a temporary override and make
170   // a second attempt.
171   if (encryption && attempt.certError) {
172     _storeCertOverride(attempt.s, host, port);
173   } else {
174     throw new Error("Connection failed");
175   }
177   attempt = await _attemptTransport(settings);
178   if (attempt.transport) {
179     // Success
180     return attempt.transport;
181   }
183   throw new Error("Connection failed even after cert override");
187  * Make a single attempt to connect and create a DevTools transport.  This could
188  * fail if the remote host is unreachable, for example.  If there is security
189  * error due to the use of self-signed certs, you should make another attempt
190  * after storing a cert override.
192  * @param host string
193  *        The host name or IP address of the devtools server.
194  * @param port number
195  *        The port number of the devtools server.
196  * @param encryption boolean (optional)
197  *        Whether the server requires encryption.  Defaults to false.
198  * @param authenticator Authenticator
199  *        |Authenticator| instance matching the mode in use by the server.
200  *        Defaults to a PROMPT instance if not supplied.
201  * @param cert object (optional)
202  *        The server's cert details.  Used with OOB_CERT authentication.
203  * @return transport DebuggerTransport
204  *         A possible DevTools transport (if connection succeeded and streams
205  *         are actually alive and working)
206  * @return certError boolean
207  *         Flag noting if cert trouble caused the streams to fail
208  * @return s nsISocketTransport
209  *         Underlying socket transport, in case more details are needed.
210  */
211 var _attemptTransport = async function(settings) {
212   const { authenticator } = settings;
213   // _attemptConnect only opens the streams.  Any failures at that stage
214   // aborts the connection process immedidately.
215   const { s, input, output } = await _attemptConnect(settings);
217   // Check if the input stream is alive.  If encryption is enabled, we need to
218   // watch out for cert errors by testing the input stream.
219   let alive, certError;
220   try {
221     const results = await _isInputAlive(input);
222     alive = results.alive;
223     certError = results.certError;
224   } catch (e) {
225     // For other unexpected errors, like NS_ERROR_CONNECTION_REFUSED, we reach
226     // this block.
227     input.close();
228     output.close();
229     throw e;
230   }
231   dumpv("Server cert accepted? " + !certError);
233   // The |Authenticator| examines the connection as well and may determine it
234   // should be dropped.
235   alive =
236     alive &&
237     authenticator.validateConnection({
238       host: settings.host,
239       port: settings.port,
240       encryption: settings.encryption,
241       cert: settings.cert,
242       socket: s,
243     });
245   let transport;
246   if (alive) {
247     transport = new DebuggerTransport(input, output);
248   } else {
249     // Something went wrong, close the streams.
250     input.close();
251     output.close();
252   }
254   return { transport, certError, s };
258  * Try to connect to a remote server socket.
260  * If successsful, the socket transport and its opened streams are returned.
261  * Typically, this will only fail if the host / port is unreachable.  Other
262  * problems, such as security errors, will allow this stage to succeed, but then
263  * fail later when the streams are actually used.
264  * @return s nsISocketTransport
265  *         Underlying socket transport, in case more details are needed.
266  * @return input nsIAsyncInputStream
267  *         The socket's input stream.
268  * @return output nsIAsyncOutputStream
269  *         The socket's output stream.
270  */
271 var _attemptConnect = async function({ host, port, encryption }) {
272   let s;
273   if (encryption) {
274     s = socketTransportService.createTransport(["ssl"], host, port, null);
275   } else {
276     s = socketTransportService.createTransport([], host, port, null);
277   }
278   // By default the CONNECT socket timeout is very long, 65535 seconds,
279   // so that if we race to be in CONNECT state while the server socket is still
280   // initializing, the connection is stuck in connecting state for 18.20 hours!
281   s.setTimeout(Ci.nsISocketTransport.TIMEOUT_CONNECT, 2);
283   // If encrypting, load the client cert now, so we can deliver it at just the
284   // right time.
285   let clientCert;
286   if (encryption) {
287     clientCert = await cert.local.getOrCreate();
288   }
290   const deferred = defer();
291   let input;
292   let output;
293   // Delay opening the input stream until the transport has fully connected.
294   // The goal is to avoid showing the user a client cert UI prompt when
295   // encryption is used.  This prompt is shown when the client opens the input
296   // stream and does not know which client cert to present to the server.  To
297   // specify a client cert programmatically, we need to access the transport's
298   // nsISSLSocketControl interface, which is not accessible until the transport
299   // has connected.
300   s.setEventSink(
301     {
302       onTransportStatus(transport, status) {
303         if (status != Ci.nsISocketTransport.STATUS_CONNECTING_TO) {
304           return;
305         }
306         if (encryption) {
307           const sslSocketControl = transport.securityInfo.QueryInterface(
308             Ci.nsISSLSocketControl
309           );
310           sslSocketControl.clientCert = clientCert;
311         }
312         try {
313           input = s.openInputStream(0, 0, 0);
314         } catch (e) {
315           deferred.reject(e);
316         }
317         deferred.resolve({ s, input, output });
318       },
319     },
320     Services.tm.currentThread
321   );
323   // openOutputStream may throw NS_ERROR_NOT_INITIALIZED if we hit some race
324   // where the nsISocketTransport gets shutdown in between its instantiation and
325   // the call to this method.
326   try {
327     output = s.openOutputStream(0, 0, 0);
328   } catch (e) {
329     deferred.reject(e);
330   }
332   deferred.promise.catch(e => {
333     if (input) {
334       input.close();
335     }
336     if (output) {
337       output.close();
338     }
339     DevToolsUtils.reportException("_attemptConnect", e);
340   });
342   return deferred.promise;
346  * Check if the input stream is alive.  For an encrypted connection, it may not
347  * be if the client refuses the server's cert.  A cert error is expected on
348  * first connection to a new host because the cert is self-signed.
349  */
350 function _isInputAlive(input) {
351   const deferred = defer();
352   input.asyncWait(
353     {
354       onInputStreamReady(stream) {
355         try {
356           stream.available();
357           deferred.resolve({ alive: true });
358         } catch (e) {
359           try {
360             // getErrorClass may throw if you pass a non-NSS error
361             const errorClass = nssErrorsService.getErrorClass(e.result);
362             if (errorClass === Ci.nsINSSErrorsService.ERROR_CLASS_BAD_CERT) {
363               deferred.resolve({ certError: true });
364             } else {
365               deferred.reject(e);
366             }
367           } catch (nssErr) {
368             deferred.reject(e);
369           }
370         }
371       },
372     },
373     0,
374     0,
375     Services.tm.currentThread
376   );
377   return deferred.promise;
381  * To allow the connection to proceed with self-signed cert, we store a cert
382  * override.  This implies that we take on the burden of authentication for
383  * these connections.
384  */
385 function _storeCertOverride(s, host, port) {
386   // eslint-disable-next-line no-shadow
387   const cert = s.securityInfo.QueryInterface(Ci.nsITransportSecurityInfo)
388     .serverCert;
389   const overrideBits =
390     Ci.nsICertOverrideService.ERROR_UNTRUSTED |
391     Ci.nsICertOverrideService.ERROR_MISMATCH;
392   certOverrideService.rememberValidityOverride(
393     host,
394     port,
395     cert,
396     overrideBits,
397     true /* temporary */
398   );
402  * Creates a new socket listener for remote connections to the DevToolsServer.
403  * This helps contain and organize the parts of the server that may differ or
404  * are particular to one given listener mechanism vs. another.
405  * This can be closed at any later time by calling |close|.
406  * If remote connections are disabled, an error is thrown.
408  * @param {DevToolsServer} devToolsServer
409  * @param {Object} socketOptions
410  *        options of socket as follows
411  *        {
412  *          authenticator:
413  *            Controls the |Authenticator| used, which hooks various socket steps to
414  *            implement an authentication policy.  It is expected that different use
415  *            cases may override pieces of the |Authenticator|.  See auth.js.
416  *            We set the default |Authenticator|, which is |Prompt|.
417  *          discoverable:
418  *            Controls whether this listener is announced via the service discovery
419  *            mechanism. Defaults is false.
420  *          encryption:
421  *            Controls whether this listener's transport uses encryption.
422  *            Defaults is false.
423  *          portOrPath:
424  *            The port or path to listen on.
425  *            If given an integer, the port to listen on.  Use -1 to choose any available
426  *            port. Otherwise, the path to the unix socket domain file to listen on.
427  *            Defaults is null.
428  *          webSocket:
429  *            Whether to use WebSocket protocol. Defaults is false.
430  *        }
431  */
432 function SocketListener(devToolsServer, socketOptions) {
433   this._devToolsServer = devToolsServer;
435   // Set socket options with default value
436   this._socketOptions = {
437     authenticator:
438       socketOptions.authenticator || new (Authenticators.get().Server)(),
439     discoverable: !!socketOptions.discoverable,
440     encryption: !!socketOptions.encryption,
441     portOrPath: socketOptions.portOrPath || null,
442     webSocket: !!socketOptions.webSocket,
443   };
445   EventEmitter.decorate(this);
448 SocketListener.prototype = {
449   get authenticator() {
450     return this._socketOptions.authenticator;
451   },
453   get discoverable() {
454     return this._socketOptions.discoverable;
455   },
457   get encryption() {
458     return this._socketOptions.encryption;
459   },
461   get portOrPath() {
462     return this._socketOptions.portOrPath;
463   },
465   get webSocket() {
466     return this._socketOptions.webSocket;
467   },
469   /**
470    * Validate that all options have been set to a supported configuration.
471    */
472   _validateOptions: function() {
473     if (this.portOrPath === null) {
474       throw new Error("Must set a port / path to listen on.");
475     }
476     if (this.discoverable && !Number(this.portOrPath)) {
477       throw new Error("Discovery only supported for TCP sockets.");
478     }
479     if (this.encryption && this.webSocket) {
480       throw new Error("Encryption not supported on WebSocket transport");
481     }
482     this.authenticator.validateOptions(this);
483   },
485   /**
486    * Listens on the given port or socket file for remote debugger connections.
487    */
488   open: function() {
489     this._validateOptions();
490     this._devToolsServer.addSocketListener(this);
492     let flags = Ci.nsIServerSocket.KeepWhenOffline;
493     // A preference setting can force binding on the loopback interface.
494     if (Services.prefs.getBoolPref("devtools.debugger.force-local")) {
495       flags |= Ci.nsIServerSocket.LoopbackOnly;
496     }
498     const self = this;
499     return (async function() {
500       const backlog = 4;
501       self._socket = self._createSocketInstance();
502       if (self.isPortBased) {
503         const port = Number(self.portOrPath);
504         self._socket.initSpecialConnection(port, flags, backlog);
505       } else if (self.portOrPath.startsWith("/")) {
506         const file = nsFile(self.portOrPath);
507         if (file.exists()) {
508           file.remove(false);
509         }
510         self._socket.initWithFilename(file, parseInt("666", 8), backlog);
511       } else {
512         // Path isn't absolute path, so we use abstract socket address
513         self._socket.initWithAbstractAddress(self.portOrPath, backlog);
514       }
515       await self._setAdditionalSocketOptions();
516       self._socket.asyncListen(self);
517       dumpn("Socket listening on: " + (self.port || self.portOrPath));
518     })()
519       .then(() => {
520         this._advertise();
521       })
522       .catch(e => {
523         dumpn(
524           "Could not start debugging listener on '" +
525             this.portOrPath +
526             "': " +
527             e
528         );
529         this.close();
530       });
531   },
533   _advertise: function() {
534     if (!this.discoverable || !this.port) {
535       return;
536     }
538     const advertisement = {
539       port: this.port,
540       encryption: this.encryption,
541     };
543     this.authenticator.augmentAdvertisement(this, advertisement);
545     discovery.addService("devtools", advertisement);
546   },
548   _createSocketInstance: function() {
549     if (this.encryption) {
550       return Cc["@mozilla.org/network/tls-server-socket;1"].createInstance(
551         Ci.nsITLSServerSocket
552       );
553     }
554     return Cc["@mozilla.org/network/server-socket;1"].createInstance(
555       Ci.nsIServerSocket
556     );
557   },
559   async _setAdditionalSocketOptions() {
560     if (this.encryption) {
561       this._socket.serverCert = await cert.local.getOrCreate();
562       this._socket.setSessionTickets(false);
563       const requestCert = Ci.nsITLSServerSocket.REQUEST_NEVER;
564       this._socket.setRequestClientCertificate(requestCert);
565     }
566     this.authenticator.augmentSocketOptions(this, this._socket);
567   },
569   /**
570    * Closes the SocketListener.  Notifies the server to remove the listener from
571    * the set of active SocketListeners.
572    */
573   close: function() {
574     if (this.discoverable && this.port) {
575       discovery.removeService("devtools");
576     }
577     if (this._socket) {
578       this._socket.close();
579       this._socket = null;
580     }
581     this._devToolsServer.removeSocketListener(this);
582   },
584   get host() {
585     if (!this._socket) {
586       return null;
587     }
588     if (Services.prefs.getBoolPref("devtools.debugger.force-local")) {
589       return "127.0.0.1";
590     }
591     return "0.0.0.0";
592   },
594   /**
595    * Gets whether this listener uses a port number vs. a path.
596    */
597   get isPortBased() {
598     return !!Number(this.portOrPath);
599   },
601   /**
602    * Gets the port that a TCP socket listener is listening on, or null if this
603    * is not a TCP socket (so there is no port).
604    */
605   get port() {
606     if (!this.isPortBased || !this._socket) {
607       return null;
608     }
609     return this._socket.port;
610   },
612   get cert() {
613     if (!this._socket || !this._socket.serverCert) {
614       return null;
615     }
616     return {
617       sha256: this._socket.serverCert.sha256Fingerprint,
618     };
619   },
621   onAllowedConnection(transport) {
622     dumpn("onAllowedConnection, transport: " + transport);
623     this.emit("accepted", transport, this);
624   },
626   // nsIServerSocketListener implementation
628   onSocketAccepted: DevToolsUtils.makeInfallible(function(
629     socket,
630     socketTransport
631   ) {
632     const connection = new ServerSocketConnection(this, socketTransport);
633     connection.once("allowed", this.onAllowedConnection.bind(this));
634   },
635   "SocketListener.onSocketAccepted"),
637   onStopListening: function(socket, status) {
638     dumpn("onStopListening, status: " + status);
639   },
642 // Client must complete TLS handshake within this window (ms)
643 loader.lazyGetter(this, "HANDSHAKE_TIMEOUT", () => {
644   return Services.prefs.getIntPref("devtools.remote.tls-handshake-timeout");
648  * A |ServerSocketConnection| is created by a |SocketListener| for each accepted
649  * incoming socket.  This is a short-lived object used to implement
650  * authentication and verify encryption prior to handing off the connection to
651  * the |DevToolsServer|.
652  */
653 function ServerSocketConnection(listener, socketTransport) {
654   this._listener = listener;
655   this._socketTransport = socketTransport;
656   this._handle();
657   EventEmitter.decorate(this);
660 ServerSocketConnection.prototype = {
661   get authentication() {
662     return this._listener.authenticator.mode;
663   },
665   get host() {
666     return this._socketTransport.host;
667   },
669   get port() {
670     return this._socketTransport.port;
671   },
673   get cert() {
674     if (!this._clientCert) {
675       return null;
676     }
677     return {
678       sha256: this._clientCert.sha256Fingerprint,
679     };
680   },
682   get address() {
683     return this.host + ":" + this.port;
684   },
686   get client() {
687     const client = {
688       host: this.host,
689       port: this.port,
690     };
691     if (this.cert) {
692       client.cert = this.cert;
693     }
694     return client;
695   },
697   get server() {
698     const server = {
699       host: this._listener.host,
700       port: this._listener.port,
701     };
702     if (this._listener.cert) {
703       server.cert = this._listener.cert;
704     }
705     return server;
706   },
708   /**
709    * This is the main authentication workflow.  If any pieces reject a promise,
710    * the connection is denied.  If the entire process resolves successfully,
711    * the connection is finally handed off to the |DevToolsServer|.
712    */
713   async _handle() {
714     dumpn("Debugging connection starting authentication on " + this.address);
715     try {
716       this._listenForTLSHandshake();
717       await this._createTransport();
718       await this._awaitTLSHandshake();
719       await this._authenticate();
720       this.allow();
721     } catch (e) {
722       this.deny(e);
723     }
724   },
726   /**
727    * We need to open the streams early on, as that is required in the case of
728    * TLS sockets to keep the handshake moving.
729    */
730   async _createTransport() {
731     const input = this._socketTransport.openInputStream(0, 0, 0);
732     const output = this._socketTransport.openOutputStream(0, 0, 0);
734     if (this._listener.webSocket) {
735       const socket = await WebSocketServer.accept(
736         this._socketTransport,
737         input,
738         output
739       );
740       this._transport = new WebSocketDebuggerTransport(socket);
741     } else {
742       this._transport = new DebuggerTransport(input, output);
743     }
745     // Start up the transport to observe the streams in case they are closed
746     // early.  This allows us to clean up our state as well.
747     this._transport.hooks = {
748       onClosed: reason => {
749         this.deny(reason);
750       },
751     };
752     this._transport.ready();
753   },
755   /**
756    * Set the socket's security observer, which receives an event via the
757    * |onHandshakeDone| callback when the TLS handshake completes.
758    */
759   _setSecurityObserver(observer) {
760     if (!this._socketTransport || !this._socketTransport.securityInfo) {
761       return;
762     }
763     const connectionInfo = this._socketTransport.securityInfo.QueryInterface(
764       Ci.nsITLSServerConnectionInfo
765     );
766     connectionInfo.setSecurityObserver(observer);
767   },
769   /**
770    * When encryption is used, we wait for the client to complete the TLS
771    * handshake before proceeding.  The handshake details are validated in
772    * |onHandshakeDone|.
773    */
774   _listenForTLSHandshake() {
775     this._handshakeDeferred = defer();
776     if (!this._listener.encryption) {
777       this._handshakeDeferred.resolve();
778       return;
779     }
780     this._setSecurityObserver(this);
781     this._handshakeTimeout = setTimeout(
782       this._onHandshakeTimeout.bind(this),
783       HANDSHAKE_TIMEOUT
784     );
785   },
787   _awaitTLSHandshake() {
788     return this._handshakeDeferred.promise;
789   },
791   _onHandshakeTimeout() {
792     dumpv("Client failed to complete TLS handshake");
793     this._handshakeDeferred.reject(Cr.NS_ERROR_NET_TIMEOUT);
794   },
796   // nsITLSServerSecurityObserver implementation
797   onHandshakeDone(socket, clientStatus) {
798     clearTimeout(this._handshakeTimeout);
799     this._setSecurityObserver(null);
800     dumpv("TLS version:    " + clientStatus.tlsVersionUsed.toString(16));
801     dumpv("TLS cipher:     " + clientStatus.cipherName);
802     dumpv("TLS key length: " + clientStatus.keyLength);
803     dumpv("TLS MAC length: " + clientStatus.macLength);
804     this._clientCert = clientStatus.peerCert;
805     /*
806      * TODO: These rules should be really be set on the TLS socket directly, but
807      * this would need more platform work to expose it via XPCOM.
808      *
809      * Enforcing cipher suites here would be a bad idea, as we want TLS
810      * cipher negotiation to work correctly.  The server already allows only
811      * Gecko's normal set of cipher suites.
812      */
813     if (clientStatus.tlsVersionUsed < Ci.nsITLSClientStatus.TLS_VERSION_1_2) {
814       this._handshakeDeferred.reject(Cr.NS_ERROR_CONNECTION_REFUSED);
815       return;
816     }
818     this._handshakeDeferred.resolve();
819   },
821   async _authenticate() {
822     const result = await this._listener.authenticator.authenticate({
823       client: this.client,
824       server: this.server,
825       transport: this._transport,
826     });
827     switch (result) {
828       case AuthenticationResult.DISABLE_ALL:
829         this._listener._devToolsServer.closeAllSocketListeners();
830         Services.prefs.setBoolPref("devtools.debugger.remote-enabled", false);
831         return promise.reject(Cr.NS_ERROR_CONNECTION_REFUSED);
832       case AuthenticationResult.DENY:
833         return promise.reject(Cr.NS_ERROR_CONNECTION_REFUSED);
834       case AuthenticationResult.ALLOW:
835       case AuthenticationResult.ALLOW_PERSIST:
836         return promise.resolve();
837       default:
838         return promise.reject(Cr.NS_ERROR_CONNECTION_REFUSED);
839     }
840   },
842   deny(result) {
843     if (this._destroyed) {
844       return;
845     }
846     let errorName = result;
847     for (const name in Cr) {
848       if (Cr[name] === result) {
849         errorName = name;
850         break;
851       }
852     }
853     dumpn(
854       "Debugging connection denied on " + this.address + " (" + errorName + ")"
855     );
856     if (this._transport) {
857       this._transport.hooks = null;
858       this._transport.close(result);
859     }
860     this._socketTransport.close(result);
861     this.destroy();
862   },
864   allow() {
865     if (this._destroyed) {
866       return;
867     }
868     dumpn("Debugging connection allowed on " + this.address);
869     this.emit("allowed", this._transport);
870     this.destroy();
871   },
873   destroy() {
874     this._destroyed = true;
875     clearTimeout(this._handshakeTimeout);
876     this._setSecurityObserver(null);
877     this._listener = null;
878     this._socketTransport = null;
879     this._transport = null;
880     this._clientCert = null;
881   },
884 exports.DebuggerSocket = DebuggerSocket;
885 exports.SocketListener = SocketListener;