X-Git-Url: http://review.tizen.org/git/?a=blobdiff_plain;f=src%2Fchrome%2Fbrowser%2Fresources%2Fcryptotoken%2Fsigner.js;h=f0af2a17ed4f64fd21c7b68d7476340d0d88ae60;hb=1afa4dd80ef85af7c90efaea6959db1d92330844;hp=cd11801c6fcaf9de53443b4088266dfccf220d40;hpb=90762837333c13ccf56f2ad88e4481fc71e8d281;p=platform%2Fframework%2Fweb%2Fcrosswalk.git diff --git a/src/chrome/browser/resources/cryptotoken/signer.js b/src/chrome/browser/resources/cryptotoken/signer.js index cd11801..f0af2a1 100644 --- a/src/chrome/browser/resources/cryptotoken/signer.js +++ b/src/chrome/browser/resources/cryptotoken/signer.js @@ -13,13 +13,13 @@ var signRequestQueue = new OriginKeyedRequestQueue(); /** * Handles a web sign request. - * @param {MessageSender} sender The sender of the message. + * @param {MessageSender} messageSender The message sender. * @param {Object} request The web page's sign request. * @param {Function} sendResponse Called back with the result of the sign. * @return {Closeable} Request handler that should be closed when the browser * message channel is closed. */ -function handleWebSignRequest(sender, request, sendResponse) { +function handleWebSignRequest(messageSender, request, sendResponse) { var sentResponse = false; var queuedSignRequest; @@ -38,6 +38,12 @@ function handleWebSignRequest(sender, request, sendResponse) { sendResponseOnce(sentResponse, queuedSignRequest, response, sendResponse); } + var sender = createSenderFromMessageSender(messageSender); + if (!sender) { + sendErrorResponse({errorCode: ErrorCodes.BAD_REQUEST}); + return null; + } + queuedSignRequest = validateAndEnqueueSignRequest( sender, request, 'signData', sendErrorResponse, @@ -47,13 +53,13 @@ function handleWebSignRequest(sender, request, sendResponse) { /** * Handles a U2F sign request. - * @param {MessageSender} sender The sender of the message. + * @param {MessageSender} messageSender The message sender. * @param {Object} request The web page's sign request. * @param {Function} sendResponse Called back with the result of the sign. * @return {Closeable} Request handler that should be closed when the browser * message channel is closed. */ -function handleU2fSignRequest(sender, request, sendResponse) { +function handleU2fSignRequest(messageSender, request, sendResponse) { var sentResponse = false; var queuedSignRequest; @@ -71,6 +77,12 @@ function handleU2fSignRequest(sender, request, sendResponse) { sendResponseOnce(sentResponse, queuedSignRequest, response, sendResponse); } + var sender = createSenderFromMessageSender(messageSender); + if (!sender) { + sendErrorResponse({errorCode: ErrorCodes.BAD_REQUEST}); + return null; + } + queuedSignRequest = validateAndEnqueueSignRequest( sender, request, 'signRequests', sendErrorResponse, @@ -120,7 +132,7 @@ function addSignatureAndBrowserDataToResponseData(responseData, signatureData, /** * Validates a sign request using the given sign challenges name, and, if valid, * enqueues the sign request for eventual processing. - * @param {MessageSender} sender The sender of the message. + * @param {WebRequestSender} sender The sender of the message. * @param {Object} request The web page's sign request. * @param {string} signChallengesName The name of the sign challenges value in * the request. @@ -131,13 +143,9 @@ function addSignatureAndBrowserDataToResponseData(responseData, signatureData, */ function validateAndEnqueueSignRequest(sender, request, signChallengesName, errorCb, successCb) { - var origin = getOriginFromUrl(/** @type {string} */ (sender.url)); - if (!origin) { - errorCb({errorCode: ErrorCodes.BAD_REQUEST}); - return null; + function timeout() { + errorCb({errorCode: ErrorCodes.TIMEOUT}); } - // More closure type inference fail. - var nonNullOrigin = /** @type {string} */ (origin); if (!isValidSignRequest(request, signChallengesName)) { errorCb({errorCode: ErrorCodes.BAD_REQUEST}); @@ -148,9 +156,7 @@ function validateAndEnqueueSignRequest(sender, request, var appId; if (request['appId']) { appId = request['appId']; - } else { - // A valid sign data has at least one challenge, so get the appId from - // the first challenge. + } else if (signChallenges.length) { appId = signChallenges[0]['appId']; } // Sanity check @@ -159,19 +165,31 @@ function validateAndEnqueueSignRequest(sender, request, errorCb({errorCode: ErrorCodes.BAD_REQUEST}); return null; } - var timer = createTimerForRequest( - FACTORY_REGISTRY.getCountdownFactory(), request); + var timeoutValueSeconds = getTimeoutValueFromRequest(request); + // Attenuate watchdog timeout value less than the signer's timeout, so the + // watchdog only fires after the signer could reasonably have called back, + // not before. + timeoutValueSeconds = attenuateTimeoutInSeconds(timeoutValueSeconds, + MINIMUM_TIMEOUT_ATTENUATION_SECONDS / 2); + var watchdog = new WatchdogRequestHandler(timeoutValueSeconds, timeout); + var wrappedErrorCb = watchdog.wrapCallback(errorCb); + var wrappedSuccessCb = watchdog.wrapCallback(successCb); + + var timer = createAttenuatedTimer( + FACTORY_REGISTRY.getCountdownFactory(), timeoutValueSeconds); var logMsgUrl = request['logMsgUrl']; // Queue sign requests from the same origin, to protect against simultaneous // sign-out on many tabs resulting in repeated sign-in requests. var queuedSignRequest = new QueuedSignRequest(signChallenges, - timer, nonNullOrigin, errorCb, successCb, appId, sender.tlsChannelId, - logMsgUrl); - var requestToken = signRequestQueue.queueRequest(appId, nonNullOrigin, + timer, sender, wrappedErrorCb, wrappedSuccessCb, request['challenge'], + appId, logMsgUrl); + var requestToken = signRequestQueue.queueRequest(appId, sender.origin, queuedSignRequest.begin.bind(queuedSignRequest), timer); queuedSignRequest.setToken(requestToken); - return queuedSignRequest; + + watchdog.setCloseable(queuedSignRequest); + return watchdog; } /** @@ -185,43 +203,46 @@ function isValidSignRequest(request, signChallengesName) { if (!request.hasOwnProperty(signChallengesName)) return false; var signChallenges = request[signChallengesName]; - // If a sign request contains an empty array of challenges, it could never - // be fulfilled. Fail. - if (!signChallenges.length) - return false; + var hasDefaultChallenge = request.hasOwnProperty('challenge'); var hasAppId = request.hasOwnProperty('appId'); - return isValidSignChallengeArray(signChallenges, !hasAppId); + // If the sign challenge array is empty, the global appId is required. + if (!hasAppId && (!signChallenges || !signChallenges.length)) { + return false; + } + return isValidSignChallengeArray(signChallenges, hasDefaultChallenge, + !hasAppId); } /** * Adapter class representing a queued sign request. * @param {!Array.} signChallenges The sign challenges. * @param {Countdown} timer Timeout timer - * @param {string} origin Signature origin + * @param {WebRequestSender} sender Message sender. * @param {function(U2fError)} errorCb Error callback * @param {function(SignChallenge, string, string)} successCb Success callback + * @param {string|undefined} opt_defaultChallenge A default sign challenge + * value, if a request does not provide one. * @param {string|undefined} opt_appId The app id for the entire request. - * @param {string|undefined} opt_tlsChannelId TLS Channel Id * @param {string|undefined} opt_logMsgUrl Url to post log messages to * @constructor * @implements {Closeable} */ -function QueuedSignRequest(signChallenges, timer, origin, errorCb, - successCb, opt_appId, opt_tlsChannelId, opt_logMsgUrl) { +function QueuedSignRequest(signChallenges, timer, sender, errorCb, + successCb, opt_defaultChallenge, opt_appId, opt_logMsgUrl) { /** @private {!Array.} */ this.signChallenges_ = signChallenges; /** @private {Countdown} */ - this.timer_ = timer; - /** @private {string} */ - this.origin_ = origin; + this.timer_ = timer.clone(this.close.bind(this)); + /** @private {WebRequestSender} */ + this.sender_ = sender; /** @private {function(U2fError)} */ this.errorCb_ = errorCb; /** @private {function(SignChallenge, string, string)} */ this.successCb_ = successCb; /** @private {string|undefined} */ - this.appId_ = opt_appId; + this.defaultChallenge_ = opt_defaultChallenge; /** @private {string|undefined} */ - this.tlsChannelId_ = opt_tlsChannelId; + this.appId_ = opt_appId; /** @private {string|undefined} */ this.logMsgUrl_ = opt_logMsgUrl; /** @private {boolean} */ @@ -233,10 +254,17 @@ function QueuedSignRequest(signChallenges, timer, origin, errorCb, /** Closes this sign request. */ QueuedSignRequest.prototype.close = function() { if (this.closed_) return; + var hadBegunSigning = false; if (this.begun_ && this.signer_) { this.signer_.close(); + hadBegunSigning = true; } if (this.token_) { + if (hadBegunSigning) { + console.log(UTIL_fmt('closing in-progress request')); + } else { + console.log(UTIL_fmt('closing timed-out request before processing')); + } this.token_.complete(); } this.closed_ = true; @@ -255,15 +283,24 @@ QueuedSignRequest.prototype.setToken = function(token) { * @param {QueuedRequestToken} token Token for this sign request. */ QueuedSignRequest.prototype.begin = function(token) { + if (this.timer_.expired()) { + console.log(UTIL_fmt('Queued request begun after timeout')); + this.close(); + this.errorCb_({errorCode: ErrorCodes.TIMEOUT}); + return; + } this.begun_ = true; this.setToken(token); - this.signer_ = new Signer(this.timer_, this.origin_, + this.signer_ = new Signer(this.timer_, this.sender_, this.signerFailed_.bind(this), this.signerSucceeded_.bind(this), - this.tlsChannelId_, this.logMsgUrl_); - if (!this.signer_.setChallenges(this.signChallenges_, this.appId_)) { + this.logMsgUrl_); + if (!this.signer_.setChallenges(this.signChallenges_, this.defaultChallenge_, + this.appId_)) { token.complete(); this.errorCb_({errorCode: ErrorCodes.BAD_REQUEST}); } + // Signer now has responsibility for maintaining timeout. + this.timer_.clearTimeout(); }; /** @@ -292,28 +329,23 @@ QueuedSignRequest.prototype.signerSucceeded_ = /** * Creates an object to track signing with a gnubby. * @param {Countdown} timer Timer for sign request. - * @param {string} origin The origin making the request. + * @param {WebRequestSender} sender The message sender. * @param {function(U2fError)} errorCb Called when the sign operation fails. * @param {function(SignChallenge, string, string)} successCb Called when the * sign operation succeeds. - * @param {string=} opt_tlsChannelId the TLS channel ID, if any, of the origin - * making the request. * @param {string=} opt_logMsgUrl The url to post log messages to. * @constructor */ -function Signer(timer, origin, errorCb, successCb, - opt_tlsChannelId, opt_logMsgUrl) { +function Signer(timer, sender, errorCb, successCb, opt_logMsgUrl) { /** @private {Countdown} */ - this.timer_ = timer; - /** @private {string} */ - this.origin_ = origin; + this.timer_ = timer.clone(); + /** @private {WebRequestSender} */ + this.sender_ = sender; /** @private {function(U2fError)} */ this.errorCb_ = errorCb; /** @private {function(SignChallenge, string, string)} */ this.successCb_ = successCb; /** @private {string|undefined} */ - this.tlsChannelId_ = opt_tlsChannelId; - /** @private {string|undefined} */ this.logMsgUrl_ = opt_logMsgUrl; /** @private {boolean} */ @@ -328,7 +360,8 @@ function Signer(timer, origin, errorCb, successCb, // Allow http appIds for http origins. (Broken, but the caller deserves // what they get.) /** @private {boolean} */ - this.allowHttp_ = this.origin_ ? this.origin_.indexOf('http://') == 0 : false; + this.allowHttp_ = this.sender_.origin ? + this.sender_.origin.indexOf('http://') == 0 : false; /** @private {Closeable} */ this.handler_ = null; } @@ -336,15 +369,24 @@ function Signer(timer, origin, errorCb, successCb, /** * Sets the challenges to be signed. * @param {Array.} signChallenges The challenges to set. + * @param {string=} opt_defaultChallenge A default sign challenge + * value, if a request does not provide one. * @param {string=} opt_appId The app id for the entire request. * @return {boolean} Whether the challenges could be set. */ -Signer.prototype.setChallenges = function(signChallenges, opt_appId) { +Signer.prototype.setChallenges = function(signChallenges, opt_defaultChallenge, + opt_appId) { if (this.challengesSet_ || this.done_) return false; + if (this.timer_.expired()) { + this.notifyError_({errorCode: ErrorCodes.TIMEOUT}); + return true; + } /** @private {Array.} */ this.signChallenges_ = signChallenges; /** @private {string|undefined} */ + this.defaultChallenge_ = opt_defaultChallenge; + /** @private {string|undefined} */ this.appId_ = opt_appId; /** @private {boolean} */ this.challengesSet_ = true; @@ -370,7 +412,8 @@ Signer.prototype.checkAppIds_ = function() { this.notifyError_(error); return; } - FACTORY_REGISTRY.getOriginChecker().canClaimAppIds(this.origin_, appIds) + FACTORY_REGISTRY.getOriginChecker() + .canClaimAppIds(this.sender_.origin, appIds) .then(this.originChecked_.bind(this, appIds)); }; @@ -393,7 +436,7 @@ Signer.prototype.originChecked_ = function(appIds, result) { } /** @private {!AppIdChecker} */ this.appIdChecker_ = new AppIdChecker(FACTORY_REGISTRY.getTextFetcher(), - this.timer_.clone(), this.origin_, + this.timer_.clone(), this.sender_.origin, /** @type {!Array.} */ (appIds), this.allowHttp_, this.logMsgUrl_); this.appIdChecker_.doCheck().then(this.appIdChecked_.bind(this)); @@ -429,17 +472,27 @@ Signer.prototype.doSign_ = function() { // Create the browser data for each challenge. for (var i = 0; i < this.signChallenges_.length; i++) { var challenge = this.signChallenges_[i]; - var serverChallenge = challenge['challenge']; + var serverChallenge; + if (challenge.hasOwnProperty('challenge')) { + serverChallenge = challenge['challenge']; + } else { + serverChallenge = this.defaultChallenge_; + } + if (!serverChallenge) { + console.warn(UTIL_fmt('challenge missing')); + return false; + } var keyHandle = challenge['keyHandle']; var browserData = - makeSignBrowserData(serverChallenge, this.origin_, this.tlsChannelId_); + makeSignBrowserData(serverChallenge, this.sender_.origin, + this.sender_.tlsChannelId); this.browserData_[keyHandle] = browserData; this.serverChallenges_[keyHandle] = challenge; } var encodedChallenges = encodeSignChallenges(this.signChallenges_, - this.appId_, this.getChallengeHash_.bind(this)); + this.defaultChallenge_, this.appId_, this.getChallengeHash_.bind(this)); var timeoutSeconds = this.timer_.millisecondsUntilExpired() / 1000.0; var request = makeSignHelperRequest(encodedChallenges, timeoutSeconds, @@ -465,6 +518,17 @@ Signer.prototype.getChallengeHash_ = function(keyHandle, challenge) { /** Closes this signer. */ Signer.prototype.close = function() { + this.close_(); +}; + +/** + * Closes this signer, and optionally notifies the caller of error. + * @param {boolean=} opt_notifying When true, this method is being called in the + * process of notifying the caller of an existing status. When false, + * the caller is notified with a default error value, ErrorCodes.TIMEOUT. + * @private + */ +Signer.prototype.close_ = function(opt_notifying) { if (this.appIdChecker_) { this.appIdChecker_.close(); } @@ -473,6 +537,9 @@ Signer.prototype.close = function() { this.handler_ = null; } this.timer_.clearTimeout(); + if (!opt_notifying) { + this.notifyError_({errorCode: ErrorCodes.TIMEOUT}); + } }; /** @@ -483,8 +550,8 @@ Signer.prototype.close = function() { Signer.prototype.notifyError_ = function(error) { if (this.done_) return; - this.close(); this.done_ = true; + this.close_(true); this.errorCb_(error); }; @@ -498,8 +565,8 @@ Signer.prototype.notifyError_ = function(error) { Signer.prototype.notifySuccess_ = function(challenge, info, browserData) { if (this.done_) return; - this.close(); this.done_ = true; + this.close_(true); this.successCb_(challenge, info, browserData); };