Bug 1103120 - Part 15: Client: Compare server cert to advertisement. r=past
authorJ. Ryan Stinnett <jryans@gmail.com>
Mon, 26 Jan 2015 12:47:13 -0600
changeset 239177 77d19984fec7f0bce8d13dc07f65f6249e32e523
parent 239176 eba034ecc1287e8c1af9d46eb76088b2fdf08aec
child 239178 e815a6d601eb0f6ea2b2b2c76dfec44a39ac86f8
push id487
push userbcampen@mozilla.com
push dateMon, 26 Jan 2015 23:32:56 +0000
reviewerspast
bugs1103120
milestone38.0a1
Bug 1103120 - Part 15: Client: Compare server cert to advertisement. r=past
toolkit/devtools/security/auth.js
toolkit/devtools/security/socket.js
--- a/toolkit/devtools/security/auth.js
+++ b/toolkit/devtools/security/auth.js
@@ -4,16 +4,18 @@
  * 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";
 
 let { Ci, Cc } = require("chrome");
 let Services = require("Services");
 let promise = require("promise");
+let DevToolsUtils = require("devtools/toolkit/DevToolsUtils");
+let { dumpn, dumpv } = DevToolsUtils;
 loader.lazyRequireGetter(this, "prompt",
   "devtools/toolkit/security/prompt");
 loader.lazyRequireGetter(this, "cert",
   "devtools/toolkit/security/cert");
 
 /**
  * A simple enum-like object with keys mirrored to values.
  * This makes comparison to a specfic value simpler without having to repeat and
@@ -87,16 +89,37 @@ let Prompt = Authenticators.Prompt = {};
 Prompt.mode = "PROMPT";
 
 Prompt.Client = function() {};
 Prompt.Client.prototype = {
 
   mode: Prompt.mode,
 
   /**
+   * When client has just made a new socket connection, validate the connection
+   * to ensure it meets the authenticator's policies.
+   *
+   * @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 cert object (optional)
+   *        The server's cert details.
+   * @param s nsISocketTransport
+   *        Underlying socket transport, in case more details are needed.
+   * @return boolean
+   *         Whether the connection is valid.
+   */
+  validateConnection() {
+    return true;
+  },
+
+  /**
    * Work with the server to complete any additional steps required by this
    * authenticator's policies.
    *
    * Debugging commences after this hook completes successfully.
    *
    * @param host string
    *        The host name or IP address of the debugger server.
    * @param port number
@@ -229,16 +252,48 @@ let OOBCert = Authenticators.OOBCert = {
 OOBCert.mode = "OOB_CERT";
 
 OOBCert.Client = function() {};
 OOBCert.Client.prototype = {
 
   mode: OOBCert.mode,
 
   /**
+   * When client has just made a new socket connection, validate the connection
+   * to ensure it meets the authenticator's policies.
+   *
+   * @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 cert object (optional)
+   *        The server's cert details.
+   * @param socket nsISocketTransport
+   *        Underlying socket transport, in case more details are needed.
+   * @return boolean
+   *         Whether the connection is valid.
+   */
+  validateConnection({ cert, socket }) {
+    // Step B.7
+    // Client verifies that Server's cert matches hash(ServerCert) from the
+    // advertisement
+    dumpv("Validate server cert hash");
+    let serverCert = socket.securityInfo.QueryInterface(Ci.nsISSLStatusProvider)
+                           .SSLStatus.serverCert;
+    let advertisedCert = cert;
+    if (serverCert.sha256Fingerprint != advertisedCert.sha256) {
+      dumpn("Server cert hash doesn't match advertisement");
+      return false;
+    }
+    return true;
+  },
+
+  /**
    * Work with the server to complete any additional steps required by this
    * authenticator's policies.
    *
    * Debugging commences after this hook completes successfully.
    *
    * @param host string
    *        The host name or IP address of the debugger server.
    * @param port number
--- a/toolkit/devtools/security/socket.js
+++ b/toolkit/devtools/security/socket.js
@@ -67,22 +67,22 @@ let DebuggerSocket = {};
  *        |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)();
+  }
   let { host, port, encryption, authenticator, cert } = settings;
   let transport = yield _getTransport(settings);
-
-  // Default to PROMPT |Authenticator| instance if not supplied
-  authenticator = authenticator || new (Authenticators.get().Client)();
-
   yield authenticator.authenticate({
     host,
     port,
     encryption,
     cert,
     transport
   });
   return transport;
@@ -93,16 +93,21 @@ DebuggerSocket.connect = Task.async(func
  * 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 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.
  */
@@ -136,34 +141,50 @@ let _getTransport = Task.async(function*
  * 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.
  */
-let _attemptTransport = Task.async(function*({ host, port, encryption }) {
+let _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({ host, port, encryption });
+  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 } = yield _isInputAlive(input);
   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();
   }