Add client cert selection logic to settings

Save the SHA-256 of the selected client cert in the connection settings.
In response to GnuTLS requests, search the list for a matching cert and
use it if it is available.

Change-Id: I0b72cac1718564d7643ae64041ec640274f76aa6
Signed-off-by: Kevin Cernekee <cernekee@chromium.org>
diff --git a/background.js b/background.js
index b321d14..d8eb744 100644
--- a/background.js
+++ b/background.js
@@ -21,6 +21,19 @@
 // user's VPN configuration (URL, username, etc.)
 var config_;
 
+function sha256(der) {
+  return crypto.subtle.digest("SHA-256", der).then(function(digest) {
+    var a = new Uint8Array(digest);
+    var ret = "";
+    for (var i = 0; i < a.length; i++) {
+      if (a[i] < 0x10)
+        ret += "0";
+      ret += a[i].toString(16);
+    }
+    return ret;
+  });
+}
+
 function startNative() {
   if (nacl_ !== null)
     return;
@@ -163,51 +176,63 @@
       certificateAuthorities: []
     }
   }, function(certlist) {
-    // TODO: pick the right cert here
     if (certlist.length === 0) {
       postMessage({"cmd": d.cmd, "success": false});
       return;
     }
-    var cert = certlist[0].certificate;
+    for (var i = 0; i < certlist.length; i++) {
+      testOneCert(d, certlist[i].certificate);
+    }
+  });
+}
 
-    if (d.cmd === "crypto-getcert") {
-      postMessage({"cmd": d.cmd, "data": cert});
+function testOneCert(d, cert) {
+  sha256(cert).then(function(digest) {
+    if (config_.clientCert === digest) {
+      console.debug("testOneCert: found " + digest);
+      if (d.cmd === "crypto-getcert") {
+        postMessage({"cmd": d.cmd, "data": cert});
+        return;
+      } else {
+        handlePrivkeySign(d, cert);
+      }
+    }
+  });
+}
+
+function handlePrivkeySign(d, cert) {
+  var keyParams = {
+    hash: {
+      name: "none"
+    },
+    name: "RSASSA-PKCS1-v1_5"
+  };
+  chrome.platformKeys.getKeyPair(cert, keyParams,
+      function(pubKey, privKey) {
+    if (privKey === null) {
+      console.error("no private key for " + d.hash);
+      postMessage({"cmd": d.cmd, "success": false});
       return;
     }
 
-    var keyParams = {
-      hash: {
-        name: "none"
-      },
-      name: "RSASSA-PKCS1-v1_5"
-    };
-    chrome.platformKeys.getKeyPair(cert, keyParams,
-        function(pubKey, privKey) {
-      if (privKey === null) {
-        console.error("no private key for " + d.hash);
-        postMessage({"cmd": d.cmd, "success": false});
-        return;
-      }
-
-      if (d.cmd === "crypto-getprivkey") {
-        postMessage({
-          "cmd": d.cmd,
-          "success": true,
-          "privkey_type": privKey.algorithm.name
-        });
-        return;
-      }
-
-      // else: d.cmd === "crypto-sign"
-
-      var sc = chrome.platformKeys.subtleCrypto();
-      var future = sc.sign(privKey.algorithm, privKey, new Uint8Array(d.data));
-      future.then(function(result) {
-        postMessage({"cmd": d.cmd, "data": result});
-      }, function() {
-        console.error("crypto-sign failed");
-        postMessage({"cmd": d.cmd, /* "data" is null */});
+    if (d.cmd === "crypto-getprivkey") {
+      postMessage({
+        "cmd": d.cmd,
+        "success": true,
+        "privkey_type": privKey.algorithm.name
       });
+      return;
+    }
+
+    // else: d.cmd === "crypto-sign"
+
+    var sc = chrome.platformKeys.subtleCrypto();
+    var future = sc.sign(privKey.algorithm, privKey, new Uint8Array(d.data));
+    future.then(function(result) {
+      postMessage({"cmd": d.cmd, "data": result});
+    }, function() {
+      console.error("crypto-sign failed");
+      postMessage({"cmd": d.cmd, /* "data" is null */});
     });
   });
 }
@@ -258,7 +283,14 @@
 
 function doConnect() {
   startNative();
-  postMessage({"cmd": "connect", "url": config_.url});
+  var msg = {
+    "cmd": "connect",
+    "url": config_.url
+  };
+  if (config_.clientCert !== undefined) {
+    msg.client_cert = "app:" + config_.clientCert;
+  }
+  postMessage(msg);
 }
 
 function clearRetryTimer() {
@@ -399,6 +431,7 @@
     }
   }, function(createdWindow) {
     createdWindow.contentWindow.reloadConfig = reloadConfig;
+    createdWindow.contentWindow.sha256 = sha256;
   });
 }
 
diff --git a/settings.html b/settings.html
index aa3d896..5334898 100644
--- a/settings.html
+++ b/settings.html
@@ -20,6 +20,9 @@
   <tr>
     <td>Password
     <td><input type="password" id="password">
+  <tr>
+    <td><button id="select-cert">Select certificate</button>
+    <td><span id="selected-cert"></span>
 </table>
 
 <button id="save">Save changes</button>
diff --git a/settings.js b/settings.js
index 006ec97..8da04dd 100644
--- a/settings.js
+++ b/settings.js
@@ -4,7 +4,7 @@
 
 "use strict";
 
-var foo;
+var clientCert = undefined;
 
 function enableSave() {
   var button = $("#save");
@@ -24,7 +24,8 @@
   var conn = {
     "url": $("#url").val(),
     "username": $("#username").val(),
-    "password": $("#password").val()
+    "password": $("#password").val(),
+    "clientCert": clientCert
   };
   chrome.storage.local.set({"connection": conn}, function() {
     disableSave();
@@ -32,8 +33,43 @@
   });
 }
 
+function setCert(digest) {
+  if (digest === undefined) {
+    $("#selected-cert").html("No certificate selected");
+    clientCert = undefined;
+  } else {
+    $("#selected-cert").html(digest);
+    clientCert = digest;
+  }
+}
+
+function selectCert() {
+  chrome.platformKeys.selectClientCertificates({
+    interactive: true,
+    request: {
+      certificateTypes: [ "rsaSign" ],
+      certificateAuthorities: []
+    }
+  }, function(certlist) {
+    if (certlist.length === 0) {
+      if (clientCert !== undefined) {
+        enableSave();
+      }
+      setCert(undefined);
+    } else {
+      sha256(certlist[0].certificate).then(function(digest) {
+        if (clientCert !== digest) {
+          enableSave();
+        }
+        setCert(digest);
+      });
+    }
+  });
+}
+
 $(document).ready(function() {
   disableSave();
+  $("#select-cert").click(selectCert);
   $("#save").click(saveChanges);
   $("#cancel").click(function() { window.close(); });
   $("input").keyup(enableSave);
@@ -44,6 +80,7 @@
       $("#url").val(conn.url);
       $("#username").val(conn.username);
       $("#password").val(conn.password);
+      setCert(conn.clientCert);
     }
   });
 });