1// Copyright 2014 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5/** 6 * @fileoverview Implements a sign handler using USB gnubbies. 7 */ 8'use strict'; 9 10var CORRUPT_sign = false; 11 12/** 13 * @param {!SignHelperRequest} request The sign request. 14 * @constructor 15 * @implements {RequestHandler} 16 */ 17function UsbSignHandler(request) { 18 /** @private {!SignHelperRequest} */ 19 this.request_ = request; 20 21 /** @private {boolean} */ 22 this.notified_ = false; 23 /** @private {boolean} */ 24 this.anyGnubbiesFound_ = false; 25} 26 27/** 28 * Default timeout value in case the caller never provides a valid timeout. 29 * @const 30 */ 31UsbSignHandler.DEFAULT_TIMEOUT_MILLIS = 30 * 1000; 32 33/** 34 * Attempts to run this handler's request. 35 * @param {RequestHandlerCallback} cb Called with the result of the request and 36 * an optional source for the sign result. 37 * @return {boolean} whether this set of challenges was accepted. 38 */ 39UsbSignHandler.prototype.run = function(cb) { 40 if (this.cb_) { 41 // Can only handle one request. 42 return false; 43 } 44 /** @private {RequestHandlerCallback} */ 45 this.cb_ = cb; 46 if (!this.request_.signData || !this.request_.signData.length) { 47 // Fail a sign request with an empty set of challenges, and pretend to have 48 // alerted the caller in case the enumerate is still pending. 49 this.notified_ = true; 50 return false; 51 } 52 var timeoutMillis = 53 this.request_.timeoutSeconds ? 54 this.request_.timeoutSeconds * 1000 : 55 UsbSignHandler.DEFAULT_TIMEOUT_MILLIS; 56 /** @private {MultipleGnubbySigner} */ 57 this.signer_ = new MultipleGnubbySigner( 58 false /* forEnroll */, 59 this.signerCompleted_.bind(this), 60 this.signerFoundGnubby_.bind(this), 61 timeoutMillis, 62 this.request_.logMsgUrl); 63 return this.signer_.doSign(this.request_.signData); 64}; 65 66 67/** 68 * Called when a MultipleGnubbySigner completes. 69 * @param {boolean} anyPending Whether any gnubbies are pending. 70 * @private 71 */ 72UsbSignHandler.prototype.signerCompleted_ = function(anyPending) { 73 if (!this.anyGnubbiesFound_ || anyPending) { 74 this.notifyError_(DeviceStatusCodes.TIMEOUT_STATUS); 75 } else if (this.signerError_ !== undefined) { 76 this.notifyError_(this.signerError_); 77 } else { 78 // Do nothing: signerFoundGnubby_ will have returned results from other 79 // gnubbies. 80 } 81}; 82 83/** 84 * Called when a MultipleGnubbySigner finds a gnubby that has completed signing 85 * its challenges. 86 * @param {MultipleSignerResult} signResult Signer result object 87 * @param {boolean} moreExpected Whether the signer expects to produce more 88 * results. 89 * @private 90 */ 91UsbSignHandler.prototype.signerFoundGnubby_ = 92 function(signResult, moreExpected) { 93 this.anyGnubbiesFound_ = true; 94 if (!signResult.code) { 95 var gnubby = signResult['gnubby']; 96 var challenge = signResult['challenge']; 97 var info = new Uint8Array(signResult['info']); 98 this.notifySuccess_(gnubby, challenge, info); 99 } else if (!moreExpected) { 100 // If the signer doesn't expect more results, return the error directly to 101 // the caller. 102 this.notifyError_(signResult.code); 103 } else { 104 // Record the last error, to report from the complete callback if no other 105 // eligible gnubbies are found. 106 /** @private {number} */ 107 this.signerError_ = signResult.code; 108 } 109}; 110 111/** 112 * Reports the result of a successful sign operation. 113 * @param {Gnubby} gnubby Gnubby instance 114 * @param {SignHelperChallenge} challenge Challenge signed 115 * @param {Uint8Array} info Result data 116 * @private 117 */ 118UsbSignHandler.prototype.notifySuccess_ = function(gnubby, challenge, info) { 119 if (this.notified_) 120 return; 121 this.notified_ = true; 122 123 gnubby.closeWhenIdle(); 124 this.close(); 125 126 if (CORRUPT_sign) { 127 CORRUPT_sign = false; 128 info[info.length - 1] = info[info.length - 1] ^ 0xff; 129 } 130 var responseData = { 131 'appIdHash': B64_encode(challenge['appIdHash']), 132 'challengeHash': B64_encode(challenge['challengeHash']), 133 'keyHandle': B64_encode(challenge['keyHandle']), 134 'signatureData': B64_encode(info) 135 }; 136 var reply = { 137 'type': 'sign_helper_reply', 138 'code': DeviceStatusCodes.OK_STATUS, 139 'responseData': responseData 140 }; 141 this.cb_(reply, 'USB'); 142}; 143 144/** 145 * Reports error to the caller. 146 * @param {number} code error to report 147 * @private 148 */ 149UsbSignHandler.prototype.notifyError_ = function(code) { 150 if (this.notified_) 151 return; 152 this.notified_ = true; 153 this.close(); 154 var reply = { 155 'type': 'sign_helper_reply', 156 'code': code 157 }; 158 this.cb_(reply); 159}; 160 161/** 162 * Closes the MultipleGnubbySigner, if any. 163 */ 164UsbSignHandler.prototype.close = function() { 165 if (this.signer_) { 166 this.signer_.close(); 167 this.signer_ = null; 168 } 169}; 170