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); } }); });