Bug 1103120 - Part 9: Server: Require client cert, add cert to session. r=past
authorJ. Ryan Stinnett <jryans@gmail.com>
Mon, 26 Jan 2015 12:47:13 -0600
changeset 239171 63a17819ae9411808096062cec4770b23c671ce7
parent 239170 37e5dfcc7674dd44d1d9574f433ab4a07a11b064
child 239172 a40e2eeacf5acc66ffa69ab0f01567b2cb1bf2a2
push id487
push userbcampen@mozilla.com
push dateMon, 26 Jan 2015 23:32:56 +0000
reviewerspast
bugs1103120
milestone38.0a1
Bug 1103120 - Part 9: Server: Require client cert, add cert to session. r=past This exposes the server and client certs as part of the session info available to authenticators and prompts.
toolkit/devtools/security/auth.js
toolkit/devtools/security/socket.js
--- a/toolkit/devtools/security/auth.js
+++ b/toolkit/devtools/security/auth.js
@@ -1,16 +1,17 @@
 /* -*- 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";
 
+let { Ci } = require("chrome");
 let Services = require("Services");
 loader.lazyRequireGetter(this, "prompt",
   "devtools/toolkit/security/prompt");
 
 /**
  * A simple enum-like object with keys mirrored to values.
  * This makes comparison to a specfic value simpler without having to repeat and
  * mis-type the value.
@@ -94,16 +95,26 @@ Prompt.Server.prototype = {
    *
    * @param listener SocketListener
    *        The socket listener about to be opened.
    * @throws if validation requirements are not met
    */
   validateOptions() {},
 
   /**
+   * Augment options on the listening socket about to be opened.
+   *
+   * @param listener SocketListener
+   *        The socket listener about to be opened.
+   * @param socket nsIServerSocket
+   *        The socket that is about to start listening.
+   */
+  augmentSocketOptions() {},
+
+  /**
    * Augment the service discovery advertisement with any additional data needed
    * to support this authentication mode.
    *
    * @param listener SocketListener
    *        The socket listener that was just opened.
    * @param advertisement object
    *        The advertisement being built.
    */
@@ -208,32 +219,43 @@ OOBCert.Server.prototype = {
    */
   validateOptions(listener) {
     if (!listener.encryption) {
       throw new Error(OOBCert.mode + " authentication requires encryption.");
     }
   },
 
   /**
+   * Augment options on the listening socket about to be opened.
+   *
+   * @param listener SocketListener
+   *        The socket listener about to be opened.
+   * @param socket nsIServerSocket
+   *        The socket that is about to start listening.
+   */
+  augmentSocketOptions(listener, socket) {
+    let requestCert = Ci.nsITLSServerSocket.REQUIRE_ALWAYS;
+    socket.setRequestClientCertificate(requestCert);
+  },
+
+  /**
    * Augment the service discovery advertisement with any additional data needed
    * to support this authentication mode.
    *
    * @param listener SocketListener
    *        The socket listener that was just opened.
    * @param advertisement object
    *        The advertisement being built.
    */
   augmentAdvertisement(listener, advertisement) {
     advertisement.authentication = OOBCert.mode;
     // Step A.4
     // Server announces itself via service discovery
     // Announcement contains hash(ServerCert) as additional data
-    advertisement.cert = {
-      sha256: listener._socket.serverCert.sha256Fingerprint
-    };
+    advertisement.cert = listener.cert;
   },
 
   /**
    * Determine whether a connection the server should be allowed or not based on
    * this authenticator's policies.
    *
    * @param session object
    *        In OOB_CERT mode, the |session| includes:
--- a/toolkit/devtools/security/socket.js
+++ b/toolkit/devtools/security/socket.js
@@ -325,16 +325,17 @@ SocketListener.prototype = {
   _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) {
@@ -370,16 +371,25 @@ SocketListener.prototype = {
    */
   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) {
@@ -414,32 +424,49 @@ ServerSocketConnection.prototype = {
   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() {
-    return {
+    let client = {
       host: this.host,
       port: this.port
     };
+    if (this.cert) {
+      client.cert = this.cert;
+    }
+    return client;
   },
 
   get server() {
-    return {
+    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() {
@@ -509,16 +536,17 @@ ServerSocketConnection.prototype = {
   // 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.
      */
@@ -572,16 +600,17 @@ ServerSocketConnection.prototype = {
   },
 
   destroy() {
     clearTimeout(this._handshakeTimeout);
     this._setSecurityObserver(null);
     this._listener = null;
     this._socketTransport = null;
     this._transport = null;
+    this._clientCert = null;
   }
 
 };
 
 DebuggerSocket.createListener = function() {
   return new SocketListener();
 };