summaryrefslogtreecommitdiffstats
path: root/devtools/shared/security/socket.js
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/shared/security/socket.js')
-rw-r--r--devtools/shared/security/socket.js793
1 files changed, 793 insertions, 0 deletions
diff --git a/devtools/shared/security/socket.js b/devtools/shared/security/socket.js
new file mode 100644
index 000000000..068a8ea81
--- /dev/null
+++ b/devtools/shared/security/socket.js
@@ -0,0 +1,793 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+var { Ci, Cc, CC, Cr, Cu } = require("chrome");
+
+// Ensure PSM is initialized to support TLS sockets
+Cc["@mozilla.org/psm;1"].getService(Ci.nsISupports);
+
+var Services = require("Services");
+var promise = require("promise");
+var defer = require("devtools/shared/defer");
+var DevToolsUtils = require("devtools/shared/DevToolsUtils");
+var { dumpn, dumpv } = DevToolsUtils;
+loader.lazyRequireGetter(this, "WebSocketServer",
+ "devtools/server/websocket-server");
+loader.lazyRequireGetter(this, "DebuggerTransport",
+ "devtools/shared/transport/transport", true);
+loader.lazyRequireGetter(this, "WebSocketDebuggerTransport",
+ "devtools/shared/transport/websocket-transport");
+loader.lazyRequireGetter(this, "DebuggerServer",
+ "devtools/server/main", true);
+loader.lazyRequireGetter(this, "discovery",
+ "devtools/shared/discovery/discovery");
+loader.lazyRequireGetter(this, "cert",
+ "devtools/shared/security/cert");
+loader.lazyRequireGetter(this, "Authenticators",
+ "devtools/shared/security/auth", true);
+loader.lazyRequireGetter(this, "AuthenticationResult",
+ "devtools/shared/security/auth", true);
+
+DevToolsUtils.defineLazyGetter(this, "nsFile", () => {
+ return CC("@mozilla.org/file/local;1", "nsIFile", "initWithPath");
+});
+
+DevToolsUtils.defineLazyGetter(this, "socketTransportService", () => {
+ return Cc["@mozilla.org/network/socket-transport-service;1"]
+ .getService(Ci.nsISocketTransportService);
+});
+
+DevToolsUtils.defineLazyGetter(this, "certOverrideService", () => {
+ return Cc["@mozilla.org/security/certoverride;1"]
+ .getService(Ci.nsICertOverrideService);
+});
+
+DevToolsUtils.defineLazyGetter(this, "nssErrorsService", () => {
+ return Cc["@mozilla.org/nss_errors_service;1"]
+ .getService(Ci.nsINSSErrorsService);
+});
+
+const { Task } = require("devtools/shared/task");
+
+var DebuggerSocket = {};
+
+/**
+ * Connects to a debugger server socket.
+ *
+ * @param host string
+ * The host name or IP address of the debugger server.
+ * @param port number
+ * The port number of the debugger server.
+ * @param encryption boolean (optional)
+ * Whether the server requires encryption. Defaults to false.
+ * @param webSocket boolean (optional)
+ * Whether to use WebSocket protocol to connect. Defaults to false.
+ * @param authenticator Authenticator (optional)
+ * |Authenticator| instance matching the mode in use by the server.
+ * Defaults to a PROMPT instance if not supplied.
+ * @param cert object (optional)
+ * The server's cert details. Used with OOB_CERT authentication.
+ * @return promise
+ * Resolved to a DebuggerTransport instance.
+ */
+DebuggerSocket.connect = Task.async(function* (settings) {
+ // Default to PROMPT |Authenticator| instance if not supplied
+ if (!settings.authenticator) {
+ settings.authenticator = new (Authenticators.get().Client)();
+ }
+ _validateSettings(settings);
+ let { host, port, encryption, authenticator, cert } = settings;
+ let transport = yield _getTransport(settings);
+ yield authenticator.authenticate({
+ host,
+ port,
+ encryption,
+ cert,
+ transport
+ });
+ transport.connectionSettings = settings;
+ return transport;
+});
+
+/**
+ * Validate that the connection settings have been set to a supported configuration.
+ */
+function _validateSettings(settings) {
+ let { encryption, webSocket, authenticator } = settings;
+
+ if (webSocket && encryption) {
+ throw new Error("Encryption not supported on WebSocket transport");
+ }
+ authenticator.validateSettings(settings);
+}
+
+/**
+ * Try very hard to create a DevTools transport, potentially making several
+ * connect attempts in the process.
+ *
+ * @param host string
+ * The host name or IP address of the debugger server.
+ * @param port number
+ * The port number of the debugger server.
+ * @param encryption boolean (optional)
+ * Whether the server requires encryption. Defaults to false.
+ * @param webSocket boolean (optional)
+ * Whether to use WebSocket protocol to connect to the server. Defaults to false.
+ * @param authenticator Authenticator
+ * |Authenticator| instance matching the mode in use by the server.
+ * Defaults to a PROMPT instance if not supplied.
+ * @param cert object (optional)
+ * The server's cert details. Used with OOB_CERT authentication.
+ * @return transport DebuggerTransport
+ * A possible DevTools transport (if connection succeeded and streams
+ * are actually alive and working)
+ */
+var _getTransport = Task.async(function* (settings) {
+ let { host, port, encryption, webSocket } = settings;
+
+ if (webSocket) {
+ // Establish a connection and wait until the WebSocket is ready to send and receive
+ let socket = yield new Promise((resolve, reject) => {
+ let s = new WebSocket(`ws://${host}:${port}`);
+ s.onopen = () => resolve(s);
+ s.onerror = err => reject(err);
+ });
+
+ return new WebSocketDebuggerTransport(socket);
+ }
+
+ let attempt = yield _attemptTransport(settings);
+ if (attempt.transport) {
+ return attempt.transport; // Success
+ }
+
+ // If the server cert failed validation, store a temporary override and make
+ // a second attempt.
+ if (encryption && attempt.certError) {
+ _storeCertOverride(attempt.s, host, port);
+ } else {
+ throw new Error("Connection failed");
+ }
+
+ attempt = yield _attemptTransport(settings);
+ if (attempt.transport) {
+ return attempt.transport; // Success
+ }
+
+ throw new Error("Connection failed even after cert override");
+});
+
+/**
+ * Make a single attempt to connect and create a DevTools transport. This could
+ * fail if the remote host is unreachable, for example. If there is security
+ * error due to the use of self-signed certs, you should make another attempt
+ * after storing a cert override.
+ *
+ * @param host string
+ * The host name or IP address of the debugger server.
+ * @param port number
+ * The port number of the debugger server.
+ * @param encryption boolean (optional)
+ * Whether the server requires encryption. Defaults to false.
+ * @param authenticator Authenticator
+ * |Authenticator| instance matching the mode in use by the server.
+ * Defaults to a PROMPT instance if not supplied.
+ * @param cert object (optional)
+ * The server's cert details. Used with OOB_CERT authentication.
+ * @return transport DebuggerTransport
+ * A possible DevTools transport (if connection succeeded and streams
+ * are actually alive and working)
+ * @return certError boolean
+ * Flag noting if cert trouble caused the streams to fail
+ * @return s nsISocketTransport
+ * Underlying socket transport, in case more details are needed.
+ */
+var _attemptTransport = Task.async(function* (settings) {
+ let { authenticator } = settings;
+ // _attemptConnect only opens the streams. Any failures at that stage
+ // aborts the connection process immedidately.
+ let { s, input, output } = yield _attemptConnect(settings);
+
+ // Check if the input stream is alive. If encryption is enabled, we need to
+ // watch out for cert errors by testing the input stream.
+ let alive, certError;
+ try {
+ let results = yield _isInputAlive(input);
+ alive = results.alive;
+ certError = results.certError;
+ } catch (e) {
+ // For other unexpected errors, like NS_ERROR_CONNECTION_REFUSED, we reach
+ // this block.
+ input.close();
+ output.close();
+ throw e;
+ }
+ dumpv("Server cert accepted? " + !certError);
+
+ // The |Authenticator| examines the connection as well and may determine it
+ // should be dropped.
+ alive = alive && authenticator.validateConnection({
+ host: settings.host,
+ port: settings.port,
+ encryption: settings.encryption,
+ cert: settings.cert,
+ socket: s
+ });
+
+ let transport;
+ if (alive) {
+ transport = new DebuggerTransport(input, output);
+ } else {
+ // Something went wrong, close the streams.
+ input.close();
+ output.close();
+ }
+
+ return { transport, certError, s };
+});
+
+/**
+ * Try to connect to a remote server socket.
+ *
+ * If successsful, the socket transport and its opened streams are returned.
+ * Typically, this will only fail if the host / port is unreachable. Other
+ * problems, such as security errors, will allow this stage to succeed, but then
+ * fail later when the streams are actually used.
+ * @return s nsISocketTransport
+ * Underlying socket transport, in case more details are needed.
+ * @return input nsIAsyncInputStream
+ * The socket's input stream.
+ * @return output nsIAsyncOutputStream
+ * The socket's output stream.
+ */
+var _attemptConnect = Task.async(function* ({ host, port, encryption }) {
+ let s;
+ if (encryption) {
+ s = socketTransportService.createTransport(["ssl"], 1, host, port, null);
+ } else {
+ s = socketTransportService.createTransport(null, 0, host, port, null);
+ }
+ // By default the CONNECT socket timeout is very long, 65535 seconds,
+ // so that if we race to be in CONNECT state while the server socket is still
+ // initializing, the connection is stuck in connecting state for 18.20 hours!
+ s.setTimeout(Ci.nsISocketTransport.TIMEOUT_CONNECT, 2);
+
+ // If encrypting, load the client cert now, so we can deliver it at just the
+ // right time.
+ let clientCert;
+ if (encryption) {
+ clientCert = yield cert.local.getOrCreate();
+ }
+
+ let deferred = defer();
+ let input;
+ let output;
+ // Delay opening the input stream until the transport has fully connected.
+ // The goal is to avoid showing the user a client cert UI prompt when
+ // encryption is used. This prompt is shown when the client opens the input
+ // stream and does not know which client cert to present to the server. To
+ // specify a client cert programmatically, we need to access the transport's
+ // nsISSLSocketControl interface, which is not accessible until the transport
+ // has connected.
+ s.setEventSink({
+ onTransportStatus(transport, status) {
+ if (status != Ci.nsISocketTransport.STATUS_CONNECTING_TO) {
+ return;
+ }
+ if (encryption) {
+ let sslSocketControl =
+ transport.securityInfo.QueryInterface(Ci.nsISSLSocketControl);
+ sslSocketControl.clientCert = clientCert;
+ }
+ try {
+ input = s.openInputStream(0, 0, 0);
+ } catch (e) {
+ deferred.reject(e);
+ }
+ deferred.resolve({ s, input, output });
+ }
+ }, Services.tm.currentThread);
+
+ // openOutputStream may throw NS_ERROR_NOT_INITIALIZED if we hit some race
+ // where the nsISocketTransport gets shutdown in between its instantiation and
+ // the call to this method.
+ try {
+ output = s.openOutputStream(0, 0, 0);
+ } catch (e) {
+ deferred.reject(e);
+ }
+
+ deferred.promise.catch(e => {
+ if (input) {
+ input.close();
+ }
+ if (output) {
+ output.close();
+ }
+ DevToolsUtils.reportException("_attemptConnect", e);
+ });
+
+ return deferred.promise;
+});
+
+/**
+ * Check if the input stream is alive. For an encrypted connection, it may not
+ * be if the client refuses the server's cert. A cert error is expected on
+ * first connection to a new host because the cert is self-signed.
+ */
+function _isInputAlive(input) {
+ let deferred = defer();
+ input.asyncWait({
+ onInputStreamReady(stream) {
+ try {
+ stream.available();
+ deferred.resolve({ alive: true });
+ } catch (e) {
+ try {
+ // getErrorClass may throw if you pass a non-NSS error
+ let errorClass = nssErrorsService.getErrorClass(e.result);
+ if (errorClass === Ci.nsINSSErrorsService.ERROR_CLASS_BAD_CERT) {
+ deferred.resolve({ certError: true });
+ } else {
+ deferred.reject(e);
+ }
+ } catch (nssErr) {
+ deferred.reject(e);
+ }
+ }
+ }
+ }, 0, 0, Services.tm.currentThread);
+ return deferred.promise;
+}
+
+/**
+ * To allow the connection to proceed with self-signed cert, we store a cert
+ * override. This implies that we take on the burden of authentication for
+ * these connections.
+ */
+function _storeCertOverride(s, host, port) {
+ let cert = s.securityInfo.QueryInterface(Ci.nsISSLStatusProvider)
+ .SSLStatus.serverCert;
+ let overrideBits = Ci.nsICertOverrideService.ERROR_UNTRUSTED |
+ Ci.nsICertOverrideService.ERROR_MISMATCH;
+ certOverrideService.rememberValidityOverride(host, port, cert, overrideBits,
+ true /* temporary */);
+}
+
+/**
+ * Creates a new socket listener for remote connections to the DebuggerServer.
+ * This helps contain and organize the parts of the server that may differ or
+ * are particular to one given listener mechanism vs. another.
+ */
+function SocketListener() {}
+
+SocketListener.prototype = {
+
+ /* Socket Options */
+
+ /**
+ * The port or path to listen on.
+ *
+ * If given an integer, the port to listen on. Use -1 to choose any available
+ * port. Otherwise, the path to the unix socket domain file to listen on.
+ */
+ portOrPath: null,
+
+ /**
+ * Controls whether this listener is announced via the service discovery
+ * mechanism.
+ */
+ discoverable: false,
+
+ /**
+ * Controls whether this listener's transport uses encryption.
+ */
+ encryption: false,
+
+ /**
+ * Controls the |Authenticator| used, which hooks various socket steps to
+ * implement an authentication policy. It is expected that different use
+ * cases may override pieces of the |Authenticator|. See auth.js.
+ *
+ * Here we set the default |Authenticator|, which is |Prompt|.
+ */
+ authenticator: new (Authenticators.get().Server)(),
+
+ /**
+ * Validate that all options have been set to a supported configuration.
+ */
+ _validateOptions: function () {
+ if (this.portOrPath === null) {
+ throw new Error("Must set a port / path to listen on.");
+ }
+ if (this.discoverable && !Number(this.portOrPath)) {
+ throw new Error("Discovery only supported for TCP sockets.");
+ }
+ if (this.encryption && this.webSocket) {
+ throw new Error("Encryption not supported on WebSocket transport");
+ }
+ this.authenticator.validateOptions(this);
+ },
+
+ /**
+ * Listens on the given port or socket file for remote debugger connections.
+ */
+ open: function () {
+ this._validateOptions();
+ DebuggerServer._addListener(this);
+
+ let flags = Ci.nsIServerSocket.KeepWhenOffline;
+ // A preference setting can force binding on the loopback interface.
+ if (Services.prefs.getBoolPref("devtools.debugger.force-local")) {
+ flags |= Ci.nsIServerSocket.LoopbackOnly;
+ }
+
+ let self = this;
+ return Task.spawn(function* () {
+ let backlog = 4;
+ self._socket = self._createSocketInstance();
+ if (self.isPortBased) {
+ let port = Number(self.portOrPath);
+ self._socket.initSpecialConnection(port, flags, backlog);
+ } else {
+ let file = nsFile(self.portOrPath);
+ if (file.exists()) {
+ file.remove(false);
+ }
+ self._socket.initWithFilename(file, parseInt("666", 8), backlog);
+ }
+ yield self._setAdditionalSocketOptions();
+ self._socket.asyncListen(self);
+ dumpn("Socket listening on: " + (self.port || self.portOrPath));
+ }).then(() => {
+ this._advertise();
+ }).catch(e => {
+ dumpn("Could not start debugging listener on '" + this.portOrPath +
+ "': " + e);
+ this.close();
+ });
+ },
+
+ _advertise: function () {
+ if (!this.discoverable || !this.port) {
+ return;
+ }
+
+ let advertisement = {
+ port: this.port,
+ encryption: this.encryption,
+ };
+
+ this.authenticator.augmentAdvertisement(this, advertisement);
+
+ discovery.addService("devtools", advertisement);
+ },
+
+ _createSocketInstance: function () {
+ if (this.encryption) {
+ return Cc["@mozilla.org/network/tls-server-socket;1"]
+ .createInstance(Ci.nsITLSServerSocket);
+ }
+ return Cc["@mozilla.org/network/server-socket;1"]
+ .createInstance(Ci.nsIServerSocket);
+ },
+
+ _setAdditionalSocketOptions: Task.async(function* () {
+ if (this.encryption) {
+ this._socket.serverCert = yield cert.local.getOrCreate();
+ this._socket.setSessionCache(false);
+ this._socket.setSessionTickets(false);
+ let requestCert = Ci.nsITLSServerSocket.REQUEST_NEVER;
+ this._socket.setRequestClientCertificate(requestCert);
+ }
+ this.authenticator.augmentSocketOptions(this, this._socket);
+ }),
+
+ /**
+ * Closes the SocketListener. Notifies the server to remove the listener from
+ * the set of active SocketListeners.
+ */
+ close: function () {
+ if (this.discoverable && this.port) {
+ discovery.removeService("devtools");
+ }
+ if (this._socket) {
+ this._socket.close();
+ this._socket = null;
+ }
+ DebuggerServer._removeListener(this);
+ },
+
+ get host() {
+ if (!this._socket) {
+ return null;
+ }
+ if (Services.prefs.getBoolPref("devtools.debugger.force-local")) {
+ return "127.0.0.1";
+ }
+ return "0.0.0.0";
+ },
+
+ /**
+ * Gets whether this listener uses a port number vs. a path.
+ */
+ get isPortBased() {
+ return !!Number(this.portOrPath);
+ },
+
+ /**
+ * Gets the port that a TCP socket listener is listening on, or null if this
+ * is not a TCP socket (so there is no port).
+ */
+ get port() {
+ if (!this.isPortBased || !this._socket) {
+ return null;
+ }
+ return this._socket.port;
+ },
+
+ get cert() {
+ if (!this._socket || !this._socket.serverCert) {
+ return null;
+ }
+ return {
+ sha256: this._socket.serverCert.sha256Fingerprint
+ };
+ },
+
+ // nsIServerSocketListener implementation
+
+ onSocketAccepted:
+ DevToolsUtils.makeInfallible(function (socket, socketTransport) {
+ new ServerSocketConnection(this, socketTransport);
+ }, "SocketListener.onSocketAccepted"),
+
+ onStopListening: function (socket, status) {
+ dumpn("onStopListening, status: " + status);
+ }
+
+};
+
+// Client must complete TLS handshake within this window (ms)
+loader.lazyGetter(this, "HANDSHAKE_TIMEOUT", () => {
+ return Services.prefs.getIntPref("devtools.remote.tls-handshake-timeout");
+});
+
+/**
+ * A |ServerSocketConnection| is created by a |SocketListener| for each accepted
+ * incoming socket. This is a short-lived object used to implement
+ * authentication and verify encryption prior to handing off the connection to
+ * the |DebuggerServer|.
+ */
+function ServerSocketConnection(listener, socketTransport) {
+ this._listener = listener;
+ this._socketTransport = socketTransport;
+ this._handle();
+}
+
+ServerSocketConnection.prototype = {
+
+ get authentication() {
+ return this._listener.authenticator.mode;
+ },
+
+ get host() {
+ return this._socketTransport.host;
+ },
+
+ get port() {
+ return this._socketTransport.port;
+ },
+
+ get cert() {
+ if (!this._clientCert) {
+ return null;
+ }
+ return {
+ sha256: this._clientCert.sha256Fingerprint
+ };
+ },
+
+ get address() {
+ return this.host + ":" + this.port;
+ },
+
+ get client() {
+ let client = {
+ host: this.host,
+ port: this.port
+ };
+ if (this.cert) {
+ client.cert = this.cert;
+ }
+ return client;
+ },
+
+ get server() {
+ let server = {
+ host: this._listener.host,
+ port: this._listener.port
+ };
+ if (this._listener.cert) {
+ server.cert = this._listener.cert;
+ }
+ return server;
+ },
+
+ /**
+ * This is the main authentication workflow. If any pieces reject a promise,
+ * the connection is denied. If the entire process resolves successfully,
+ * the connection is finally handed off to the |DebuggerServer|.
+ */
+ _handle() {
+ dumpn("Debugging connection starting authentication on " + this.address);
+ let self = this;
+ Task.spawn(function* () {
+ self._listenForTLSHandshake();
+ yield self._createTransport();
+ yield self._awaitTLSHandshake();
+ yield self._authenticate();
+ }).then(() => this.allow()).catch(e => this.deny(e));
+ },
+
+ /**
+ * We need to open the streams early on, as that is required in the case of
+ * TLS sockets to keep the handshake moving.
+ */
+ _createTransport: Task.async(function* () {
+ let input = this._socketTransport.openInputStream(0, 0, 0);
+ let output = this._socketTransport.openOutputStream(0, 0, 0);
+
+ if (this._listener.webSocket) {
+ let socket = yield WebSocketServer.accept(this._socketTransport, input, output);
+ this._transport = new WebSocketDebuggerTransport(socket);
+ } else {
+ this._transport = new DebuggerTransport(input, output);
+ }
+
+ // Start up the transport to observe the streams in case they are closed
+ // early. This allows us to clean up our state as well.
+ this._transport.hooks = {
+ onClosed: reason => {
+ this.deny(reason);
+ }
+ };
+ this._transport.ready();
+ }),
+
+ /**
+ * Set the socket's security observer, which receives an event via the
+ * |onHandshakeDone| callback when the TLS handshake completes.
+ */
+ _setSecurityObserver(observer) {
+ if (!this._socketTransport || !this._socketTransport.securityInfo) {
+ return;
+ }
+ let connectionInfo = this._socketTransport.securityInfo
+ .QueryInterface(Ci.nsITLSServerConnectionInfo);
+ connectionInfo.setSecurityObserver(observer);
+ },
+
+ /**
+ * When encryption is used, we wait for the client to complete the TLS
+ * handshake before proceeding. The handshake details are validated in
+ * |onHandshakeDone|.
+ */
+ _listenForTLSHandshake() {
+ this._handshakeDeferred = defer();
+ if (!this._listener.encryption) {
+ this._handshakeDeferred.resolve();
+ return;
+ }
+ this._setSecurityObserver(this);
+ this._handshakeTimeout = setTimeout(this._onHandshakeTimeout.bind(this),
+ HANDSHAKE_TIMEOUT);
+ },
+
+ _awaitTLSHandshake() {
+ return this._handshakeDeferred.promise;
+ },
+
+ _onHandshakeTimeout() {
+ dumpv("Client failed to complete TLS handshake");
+ this._handshakeDeferred.reject(Cr.NS_ERROR_NET_TIMEOUT);
+ },
+
+ // nsITLSServerSecurityObserver implementation
+ onHandshakeDone(socket, clientStatus) {
+ clearTimeout(this._handshakeTimeout);
+ this._setSecurityObserver(null);
+ dumpv("TLS version: " + clientStatus.tlsVersionUsed.toString(16));
+ dumpv("TLS cipher: " + clientStatus.cipherName);
+ dumpv("TLS key length: " + clientStatus.keyLength);
+ dumpv("TLS MAC length: " + clientStatus.macLength);
+ this._clientCert = clientStatus.peerCert;
+ /*
+ * TODO: These rules should be really be set on the TLS socket directly, but
+ * this would need more platform work to expose it via XPCOM.
+ *
+ * Enforcing cipher suites here would be a bad idea, as we want TLS
+ * cipher negotiation to work correctly. The server already allows only
+ * Gecko's normal set of cipher suites.
+ */
+ if (clientStatus.tlsVersionUsed < Ci.nsITLSClientStatus.TLS_VERSION_1_2) {
+ this._handshakeDeferred.reject(Cr.NS_ERROR_CONNECTION_REFUSED);
+ return;
+ }
+
+ this._handshakeDeferred.resolve();
+ },
+
+ _authenticate: Task.async(function* () {
+ let result = yield this._listener.authenticator.authenticate({
+ client: this.client,
+ server: this.server,
+ transport: this._transport
+ });
+ switch (result) {
+ case AuthenticationResult.DISABLE_ALL:
+ DebuggerServer.closeAllListeners();
+ Services.prefs.setBoolPref("devtools.debugger.remote-enabled", false);
+ return promise.reject(Cr.NS_ERROR_CONNECTION_REFUSED);
+ case AuthenticationResult.DENY:
+ return promise.reject(Cr.NS_ERROR_CONNECTION_REFUSED);
+ case AuthenticationResult.ALLOW:
+ case AuthenticationResult.ALLOW_PERSIST:
+ return promise.resolve();
+ default:
+ return promise.reject(Cr.NS_ERROR_CONNECTION_REFUSED);
+ }
+ }),
+
+ deny(result) {
+ if (this._destroyed) {
+ return;
+ }
+ let errorName = result;
+ for (let name in Cr) {
+ if (Cr[name] === result) {
+ errorName = name;
+ break;
+ }
+ }
+ dumpn("Debugging connection denied on " + this.address +
+ " (" + errorName + ")");
+ if (this._transport) {
+ this._transport.hooks = null;
+ this._transport.close(result);
+ }
+ this._socketTransport.close(result);
+ this.destroy();
+ },
+
+ allow() {
+ if (this._destroyed) {
+ return;
+ }
+ dumpn("Debugging connection allowed on " + this.address);
+ DebuggerServer._onConnection(this._transport);
+ this.destroy();
+ },
+
+ destroy() {
+ this._destroyed = true;
+ clearTimeout(this._handshakeTimeout);
+ this._setSecurityObserver(null);
+ this._listener = null;
+ this._socketTransport = null;
+ this._transport = null;
+ this._clientCert = null;
+ }
+
+};
+
+DebuggerSocket.createListener = function () {
+ return new SocketListener();
+};
+
+exports.DebuggerSocket = DebuggerSocket;