@@ -11,10 +11,11 @@ /* * Handle for the CACKey NaCl Target */ var cackeyHandle = null; var cackeyPCSCHandle = null; +var cackeyPCSCHandleUsable = false; /* * Handle and ID for outstanding callbacks */ var cackeyOutstandingCallbacks = {} @@ -34,10 +35,15 @@ /* * Stored PIN for a given certificate */ var cackeyCertificateToPINMap = {}; +/* + * Callbacks to perform after PCSC comes online + */ +cackeyCallbackAfterInit = [] + /* * Compute a text-based handle for a certificate to be hashed by */ function cackeyCertificateToPINID(certificate) { var id; @@ -67,11 +73,11 @@ for (idx = 0; idx < message.certificates.length; idx++) { certificates.push( { certificate: message.certificates[idx], - supportedHashes: ['SHA1', 'SHA256'] + supportedHashes: ['SHA1', 'SHA256', 'MD5_SHA1'] } ); } chromeCallback(certificates, @@ -85,10 +91,12 @@ } return; } ); + + return; } /* * Handle a response from the NaCl side regarding signing a message */ @@ -96,10 +104,12 @@ var payload; payload = message.signedData; chromeCallback(payload); + + return; } /* * Handle an incoming message from the NaCl side and pass it off to * one of the handlers above for actual formatting and passing to @@ -229,11 +239,13 @@ } else { tmpMessageEvent.data.originalrequest.pin = pinWindowPINValue; cackeyCertificateToPINMap[cackeyCertificateToPINID(tmpMessageEvent.data.originalrequest.certificate)] = pinWindowPINValue; - cackeyHandle.postMessage(tmpMessageEvent.data.originalrequest); + cackeyInitPCSC(function() { + cackeyHandle.postMessage(tmpMessageEvent.data.originalrequest); + }); } } /* @@ -299,24 +311,26 @@ console.log("[cackey] Asked to provide a list of certificates -- throwing that request over to the NaCl side... "); } callbackId = cackeyOutstandingCallbackCounter + 1; - cackeyHandle.postMessage( - { - 'target': "cackey", - 'command': "listcertificates", - 'id': callbackId - } - ); - - cackeyOutstandingCallbackCounter = callbackId; - cackeyOutstandingCallbacks[callbackId] = chromeCallback; - - if (GoogleSmartCard.IS_DEBUG_BUILD) { - console.log("[cackey] Thrown."); - } + cackeyInitPCSC(function() { + cackeyHandle.postMessage( + { + 'target': "cackey", + 'command': "listcertificates", + 'id': callbackId + } + ); + + cackeyOutstandingCallbackCounter = callbackId; + cackeyOutstandingCallbacks[callbackId] = chromeCallback; + + if (GoogleSmartCard.IS_DEBUG_BUILD) { + console.log("[cackey] Thrown."); + } + }); return; } /* @@ -373,54 +387,85 @@ if (cackeyCertificateToPINMap[certificateId]) { command.pin = cackeyCertificateToPINMap[certificateId]; } - cackeyHandle.postMessage(command); - - cackeyOutstandingCallbackCounter = callbackId; - cackeyOutstandingCallbacks[callbackId] = chromeCallback; - - if (GoogleSmartCard.IS_DEBUG_BUILD) { - console.log("[cackey] Thrown."); - } + cackeyInitPCSC(function() { + cackeyHandle.postMessage(command); + + cackeyOutstandingCallbackCounter = callbackId; + cackeyOutstandingCallbacks[callbackId] = chromeCallback; + + if (GoogleSmartCard.IS_DEBUG_BUILD) { + console.log("[cackey] Thrown."); + } + }); + + return; +} + +/* + * Unititalizes the CACKey PCSC connection + */ +function cackeyUninitPCSC() { + console.log("[cackey] cackeyUninitPCSC() called"); + + if (cackeyPCSCHandle != null) { + console.log("[cackey] Deleting PCSC handle"); + + delete cackeyPCSCHandle; + + cackeyPCSCHandle = null; + } + + cackeyPCSCHandleUsable = false; + + console.log("[cackey] cackeyUninitPCSC() returning"); return; } /* * Uninitializes CACKey (probably due to a crash) */ function cackeyUninit() { + console.log("[cackey] cackeyUninit() called"); + if (chrome.certificateProvider) { + console.log("[cackey] Unregistered Chrome certificate handlers"); + chrome.certificateProvider.onCertificatesRequested.removeListener(cackeyListCertificates); chrome.certificateProvider.onSignDigestRequested.removeListener(cackeySignMessage); } - if (cackeyPCSCHandle != null) { - delete cackeyPCSCHandle; - - cackeyPCSCHandle = null; - } + cackeyUninitPCSC(); if (cackeyHandle != null) { + console.log("[cackey] Deleting PNaCl module"); + try { document.body.removeChild(cackeyHandle); } catch (e) { } delete cackeyHandle; cackeyHandle = null; } + + console.log("[cackey] cackeyUninit() complete"); + + return; } /* * Restarts CACKey */ function cackeyRestart() { cackeyUninit(); cackeyInit(); + + return; } /* * Handle a CACKey crash (probably due to loss of connectivity to the PCSC daemon) */ @@ -428,22 +473,75 @@ /* * Schedule the restart to occur in 30 seconds in case we really are * not working. */ setTimeout(cackeyRestart, 30000); + + return; +} + +function cackeyInitPCSCCompleted() { + var idx; + + cackeyPCSCHandleUsable = true; + + for (idx = 0; idx < cackeyCallbackAfterInit.length; idx++) { + if (!cackeyCallbackAfterInit[idx]) { + continue; + } + + cackeyCallbackAfterInit[idx](); + } + + delete cackeyCallbackAfterInit; + + cackeyCallbackAfterInit = []; + + return; } /* - * Finish performing initialization that must wait until we have loaded the CACKey module + * Initialize the PCSC connection */ -function cackeyInitLoaded(messageEvent) { - console.log("[cackey] Loaded CACKey PNaCl Module"); +function cackeyInitPCSC(callbackAfterInit) { + /* + * Start the Google PCSC Interface + */ + + console.log("[cackey] cackeyInitPCSC() called"); + + /* + * Queue this callback to be completed when initialization is complete + */ + if (callbackAfterInit) { + cackeyCallbackAfterInit.push(callbackAfterInit); + } + + /* + * No additional work is required + */ + + if (cackeyPCSCHandle) { + console.log("[cackey] PCSC handle is already valid, nothing to do."); + + if (cackeyPCSCHandleUsable) { + cackeyInitPCSCCompleted(); + } + + return; + } + + /* + * Sanely initialize this + */ + cackeyPCSCHandleUsable = false; - /* Register listeners with Chrome */ - if (chrome.certificateProvider) { - chrome.certificateProvider.onCertificatesRequested.addListener(cackeyListCertificates); - chrome.certificateProvider.onSignDigestRequested.addListener(cackeySignMessage); + /* + * Initialize the CACKey PNaCl module if needed + */ + if (cackeyHandle == null) { + cackeyInit(); } /* * Initialize CACKey with the correct handle to talk to the Google Smartcard Manager App */ @@ -454,13 +552,32 @@ "smartcardManagerAppId": "khpfeaanjngmcnplbdlpegiifgpfgdco" } ); /* - * Start the Google PCSC Interface + * Initialize the PCSC NaCl interface */ cackeyPCSCHandle = new GoogleSmartCard.PcscNacl(cackeyHandle); + + console.log("[cackey] cackeyInitPCSC() complete"); + + return; +} + +/* + * Finish performing initialization that must wait until we have loaded the CACKey module + */ +function cackeyInitLoaded(messageEvent) { + console.log("[cackey] Loaded CACKey PNaCl Module"); + + /* Register listeners with Chrome */ + if (chrome.certificateProvider) { + console.log("[cackey] Registered Certificate handlers with Chrome"); + + chrome.certificateProvider.onCertificatesRequested.addListener(cackeyListCertificates); + chrome.certificateProvider.onSignDigestRequested.addListener(cackeySignMessage); + } return; } /* @@ -513,16 +630,21 @@ * by requesting its position */ forceLoadElement = cackeyHandle.offsetTop; console.log("[cackey] cackeyInit(): Completed. Returning."); + + return; } /* * Initialize the CACKey Chrome Application */ function cackeyAppInit() { + var oldOnPortDisconnectedFunction; + var oldPCSCInitializationCallback; + /* * Create a handler for starting the application UI */ chrome.app.runtime.onLaunched.addListener(function() { chrome.app.window.create('ui.html', { @@ -534,10 +656,36 @@ "height": 135, "minHeight": 135 } }); }); + + /* + * Register a handler for dealing with the PCSC port being disconnected + */ + oldOnPortDisconnectedFunction = GoogleSmartCard.Pcsc.prototype.onPortDisconnected_; + GoogleSmartCard.Pcsc.prototype.onPortDisconnected_ = function() { + oldOnPortDisconnectedFunction.apply(this); + + cackeyRestart(); + + return; + }; + + /* + * Register a handler for dealing with the PCSC port being available + */ + oldPCSCInitializationCallback = GoogleSmartCard.PcscNacl.prototype.pcscInitializationCallback_; + GoogleSmartCard.PcscNacl.prototype.pcscInitializationCallback_ = function(requestId, instanceId, instance, error) { + oldPCSCInitializationCallback.apply(this, [requestId, instanceId, instance, error]); + + cackeyInitPCSCCompleted(); + + return; + }; + + return; } /* Initialize CACKey */ cackeyAppInit(); cackeyInit();