Roll src/third_party/WebKit bf18a82:a9cee16 (svn 185297:185304)
[chromium-blink-merge.git] / chrome / browser / resources / cryptotoken / webrequest.js
blobd336e4294373442666cf39e453d72cb4a5e1f0d8
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.
5 /**
6 * @fileoverview Does common handling for requests coming from web pages and
7 * routes them to the provided handler.
8 */
10 /**
11 * Gets the scheme + origin from a web url.
12 * @param {string} url Input url
13 * @return {?string} Scheme and origin part if url parses
15 function getOriginFromUrl(url) {
16 var re = new RegExp('^(https?://)[^/]*/?');
17 var originarray = re.exec(url);
18 if (originarray == null) return originarray;
19 var origin = originarray[0];
20 while (origin.charAt(origin.length - 1) == '/') {
21 origin = origin.substring(0, origin.length - 1);
23 if (origin == 'http:' || origin == 'https:')
24 return null;
25 return origin;
28 /**
29 * Returns whether the registered key appears to be valid.
30 * @param {Object} registeredKey The registered key object.
31 * @param {boolean} appIdRequired Whether the appId property is required on
32 * each challenge.
33 * @return {boolean} Whether the object appears valid.
35 function isValidRegisteredKey(registeredKey, appIdRequired) {
36 if (appIdRequired && !registeredKey.hasOwnProperty('appId')) {
37 return false;
39 if (!registeredKey.hasOwnProperty('keyHandle'))
40 return false;
41 if (registeredKey['version']) {
42 if (registeredKey['version'] != 'U2F_V1' &&
43 registeredKey['version'] != 'U2F_V2') {
44 return false;
47 return true;
50 /**
51 * Returns whether the array of registered keys appears to be valid.
52 * @param {Array.<Object>} registeredKeys The array of registered keys.
53 * @param {boolean} appIdRequired Whether the appId property is required on
54 * each challenge.
55 * @return {boolean} Whether the array appears valid.
57 function isValidRegisteredKeyArray(registeredKeys, appIdRequired) {
58 return registeredKeys.every(function(key) {
59 return isValidRegisteredKey(key, appIdRequired);
60 });
63 /**
64 * Returns whether the array of SignChallenges appears to be valid.
65 * @param {Array.<SignChallenge>} signChallenges The array of sign challenges.
66 * @param {boolean} challengeValueRequired Whether each challenge object
67 * requires a challenge value.
68 * @param {boolean} appIdRequired Whether the appId property is required on
69 * each challenge.
70 * @return {boolean} Whether the array appears valid.
72 function isValidSignChallengeArray(signChallenges, challengeValueRequired,
73 appIdRequired) {
74 for (var i = 0; i < signChallenges.length; i++) {
75 var incomingChallenge = signChallenges[i];
76 if (challengeValueRequired &&
77 !incomingChallenge.hasOwnProperty('challenge'))
78 return false;
79 if (!isValidRegisteredKey(incomingChallenge, appIdRequired)) {
80 return false;
83 return true;
86 /** Posts the log message to the log url.
87 * @param {string} logMsg the log message to post.
88 * @param {string=} opt_logMsgUrl the url to post log messages to.
90 function logMessage(logMsg, opt_logMsgUrl) {
91 console.log(UTIL_fmt('logMessage("' + logMsg + '")'));
93 if (!opt_logMsgUrl) {
94 return;
96 // Image fetching is not allowed per packaged app CSP.
97 // But video and audio is.
98 var audio = new Audio();
99 audio.src = opt_logMsgUrl + logMsg;
103 * @param {Object} request Request object
104 * @param {MessageSender} sender Sender frame
105 * @param {Function} sendResponse Response callback
106 * @return {?Closeable} Optional handler object that should be closed when port
107 * closes
109 function handleWebPageRequest(request, sender, sendResponse) {
110 switch (request.type) {
111 case GnubbyMsgTypes.ENROLL_WEB_REQUEST:
112 return handleWebEnrollRequest(sender, request, sendResponse);
114 case GnubbyMsgTypes.SIGN_WEB_REQUEST:
115 return handleWebSignRequest(sender, request, sendResponse);
117 case MessageTypes.U2F_REGISTER_REQUEST:
118 return handleU2fEnrollRequest(sender, request, sendResponse);
120 case MessageTypes.U2F_SIGN_REQUEST:
121 return handleU2fSignRequest(sender, request, sendResponse);
123 default:
124 sendResponse(
125 makeU2fErrorResponse(request, ErrorCodes.BAD_REQUEST, undefined,
126 MessageTypes.U2F_REGISTER_RESPONSE));
127 return null;
132 * Set-up listeners for webpage connect.
133 * @param {Object} port connection is on.
134 * @param {Object} request that got received on port.
136 function handleWebPageConnect(port, request) {
137 var closeable;
139 var onMessage = function(request) {
140 console.log(UTIL_fmt('request'));
141 console.log(request);
142 closeable = handleWebPageRequest(request, port.sender,
143 function(response) {
144 response['requestId'] = request['requestId'];
145 port.postMessage(response);
149 var onDisconnect = function() {
150 port.onMessage.removeListener(onMessage);
151 port.onDisconnect.removeListener(onDisconnect);
152 if (closeable) closeable.close();
155 port.onMessage.addListener(onMessage);
156 port.onDisconnect.addListener(onDisconnect);
158 // Start work on initial message.
159 onMessage(request);
163 * Makes a response to a request.
164 * @param {Object} request The request to make a response to.
165 * @param {string} responseSuffix How to name the response's type.
166 * @param {string=} opt_defaultType The default response type, if none is
167 * present in the request.
168 * @return {Object} The response object.
170 function makeResponseForRequest(request, responseSuffix, opt_defaultType) {
171 var type;
172 if (request && request.type) {
173 type = request.type.replace(/_request$/, responseSuffix);
174 } else {
175 type = opt_defaultType;
177 var reply = { 'type': type };
178 if (request && request.requestId) {
179 reply.requestId = request.requestId;
181 return reply;
185 * Makes a response to a U2F request with an error code.
186 * @param {Object} request The request to make a response to.
187 * @param {ErrorCodes} code The error code to return.
188 * @param {string=} opt_detail An error detail string.
189 * @param {string=} opt_defaultType The default response type, if none is
190 * present in the request.
191 * @return {Object} The U2F error.
193 function makeU2fErrorResponse(request, code, opt_detail, opt_defaultType) {
194 var reply = makeResponseForRequest(request, '_response', opt_defaultType);
195 var error = {'errorCode': code};
196 if (opt_detail) {
197 error['errorMessage'] = opt_detail;
199 reply['responseData'] = error;
200 return reply;
204 * Makes a success response to a web request with a responseData object.
205 * @param {Object} request The request to make a response to.
206 * @param {Object} responseData The response data.
207 * @return {Object} The web error.
209 function makeU2fSuccessResponse(request, responseData) {
210 var reply = makeResponseForRequest(request, '_response');
211 reply['responseData'] = responseData;
212 return reply;
216 * Makes a response to a web request with an error code.
217 * @param {Object} request The request to make a response to.
218 * @param {GnubbyCodeTypes} code The error code to return.
219 * @param {string=} opt_defaultType The default response type, if none is
220 * present in the request.
221 * @return {Object} The web error.
223 function makeWebErrorResponse(request, code, opt_defaultType) {
224 var reply = makeResponseForRequest(request, '_reply', opt_defaultType);
225 reply['code'] = code;
226 return reply;
230 * Makes a success response to a web request with a responseData object.
231 * @param {Object} request The request to make a response to.
232 * @param {Object} responseData The response data.
233 * @return {Object} The web error.
235 function makeWebSuccessResponse(request, responseData) {
236 var reply = makeResponseForRequest(request, '_reply');
237 reply['code'] = GnubbyCodeTypes.OK;
238 reply['responseData'] = responseData;
239 return reply;
243 * Maps an error code from the ErrorCodes namespace to the GnubbyCodeTypes
244 * namespace.
245 * @param {ErrorCodes} errorCode Error in the ErrorCodes namespace.
246 * @param {boolean} forSign Whether the error is for a sign request.
247 * @return {GnubbyCodeTypes} Error code in the GnubbyCodeTypes namespace.
249 function mapErrorCodeToGnubbyCodeType(errorCode, forSign) {
250 var code;
251 switch (errorCode) {
252 case ErrorCodes.BAD_REQUEST:
253 return GnubbyCodeTypes.BAD_REQUEST;
255 case ErrorCodes.DEVICE_INELIGIBLE:
256 return forSign ? GnubbyCodeTypes.NONE_PLUGGED_ENROLLED :
257 GnubbyCodeTypes.ALREADY_ENROLLED;
259 case ErrorCodes.TIMEOUT:
260 return GnubbyCodeTypes.WAIT_TOUCH;
263 return GnubbyCodeTypes.UNKNOWN_ERROR;
267 * Maps a helper's error code from the DeviceStatusCodes namespace to a
268 * U2fError.
269 * @param {number} code Error code from DeviceStatusCodes namespace.
270 * @return {U2fError} An error.
272 function mapDeviceStatusCodeToU2fError(code) {
273 switch (code) {
274 case DeviceStatusCodes.WRONG_DATA_STATUS:
275 return {errorCode: ErrorCodes.DEVICE_INELIGIBLE};
277 case DeviceStatusCodes.TIMEOUT_STATUS:
278 case DeviceStatusCodes.WAIT_TOUCH_STATUS:
279 return {errorCode: ErrorCodes.TIMEOUT};
282 default:
283 var reportedError = {
284 errorCode: ErrorCodes.OTHER_ERROR,
285 errorMessage: 'device status code: ' + code.toString(16)
287 return reportedError;
292 * Sends a response, using the given sentinel to ensure at most one response is
293 * sent. Also closes the closeable, if it's given.
294 * @param {boolean} sentResponse Whether a response has already been sent.
295 * @param {?Closeable} closeable A thing to close.
296 * @param {*} response The response to send.
297 * @param {Function} sendResponse A function to send the response.
299 function sendResponseOnce(sentResponse, closeable, response, sendResponse) {
300 if (closeable) {
301 closeable.close();
303 if (!sentResponse) {
304 sentResponse = true;
305 try {
306 // If the page has gone away or the connection has otherwise gone,
307 // sendResponse fails.
308 sendResponse(response);
309 } catch (exception) {
310 console.warn('sendResponse failed: ' + exception);
312 } else {
313 console.warn(UTIL_fmt('Tried to reply more than once! Juan, FIX ME'));
318 * @param {!string} string Input string
319 * @return {Array.<number>} SHA256 hash value of string.
321 function sha256HashOfString(string) {
322 var s = new SHA256();
323 s.update(UTIL_StringToBytes(string));
324 return s.digest();
328 * Normalizes the TLS channel ID value:
329 * 1. Converts semantically empty values (undefined, null, 0) to the empty
330 * string.
331 * 2. Converts valid JSON strings to a JS object.
332 * 3. Otherwise, returns the input value unmodified.
333 * @param {Object|string|undefined} opt_tlsChannelId TLS Channel id
334 * @return {Object|string} The normalized TLS channel ID value.
336 function tlsChannelIdValue(opt_tlsChannelId) {
337 if (!opt_tlsChannelId) {
338 // Case 1: Always set some value for TLS channel ID, even if it's the empty
339 // string: this browser definitely supports them.
340 return '';
342 if (typeof opt_tlsChannelId === 'string') {
343 try {
344 var obj = JSON.parse(opt_tlsChannelId);
345 if (!obj) {
346 // Case 1: The string value 'null' parses as the Javascript object null,
347 // so return an empty string: the browser definitely supports TLS
348 // channel id.
349 return '';
351 // Case 2: return the value as a JS object.
352 return /** @type {Object} */ (obj);
353 } catch (e) {
354 console.warn('Unparseable TLS channel ID value ' + opt_tlsChannelId);
355 // Case 3: return the value unmodified.
358 return opt_tlsChannelId;
362 * Creates a browser data object with the given values.
363 * @param {!string} type A string representing the "type" of this browser data
364 * object.
365 * @param {!string} serverChallenge The server's challenge, as a base64-
366 * encoded string.
367 * @param {!string} origin The server's origin, as seen by the browser.
368 * @param {Object|string|undefined} opt_tlsChannelId TLS Channel Id
369 * @return {string} A string representation of the browser data object.
371 function makeBrowserData(type, serverChallenge, origin, opt_tlsChannelId) {
372 var browserData = {
373 'typ' : type,
374 'challenge' : serverChallenge,
375 'origin' : origin
377 if (BROWSER_SUPPORTS_TLS_CHANNEL_ID) {
378 browserData['cid_pubkey'] = tlsChannelIdValue(opt_tlsChannelId);
380 return JSON.stringify(browserData);
384 * Creates a browser data object for an enroll request with the given values.
385 * @param {!string} serverChallenge The server's challenge, as a base64-
386 * encoded string.
387 * @param {!string} origin The server's origin, as seen by the browser.
388 * @param {Object|string|undefined} opt_tlsChannelId TLS Channel Id
389 * @return {string} A string representation of the browser data object.
391 function makeEnrollBrowserData(serverChallenge, origin, opt_tlsChannelId) {
392 return makeBrowserData(
393 'navigator.id.finishEnrollment', serverChallenge, origin,
394 opt_tlsChannelId);
398 * Creates a browser data object for a sign request with the given values.
399 * @param {!string} serverChallenge The server's challenge, as a base64-
400 * encoded string.
401 * @param {!string} origin The server's origin, as seen by the browser.
402 * @param {Object|string|undefined} opt_tlsChannelId TLS Channel Id
403 * @return {string} A string representation of the browser data object.
405 function makeSignBrowserData(serverChallenge, origin, opt_tlsChannelId) {
406 return makeBrowserData(
407 'navigator.id.getAssertion', serverChallenge, origin, opt_tlsChannelId);
411 * Encodes the sign data as an array of sign helper challenges.
412 * @param {Array.<SignChallenge>} signChallenges The sign challenges to encode.
413 * @param {string|undefined} opt_defaultChallenge A default sign challenge
414 * value, if a request does not provide one.
415 * @param {string=} opt_defaultAppId The app id to use for each challenge, if
416 * the challenge contains none.
417 * @param {function(string, string): string=} opt_challengeHashFunction
418 * A function that produces, from a key handle and a raw challenge, a hash
419 * of the raw challenge. If none is provided, a default hash function is
420 * used.
421 * @return {!Array.<SignHelperChallenge>} The sign challenges, encoded.
423 function encodeSignChallenges(signChallenges, opt_defaultChallenge,
424 opt_defaultAppId, opt_challengeHashFunction) {
425 function encodedSha256(keyHandle, challenge) {
426 return B64_encode(sha256HashOfString(challenge));
428 var challengeHashFn = opt_challengeHashFunction || encodedSha256;
429 var encodedSignChallenges = [];
430 if (signChallenges) {
431 for (var i = 0; i < signChallenges.length; i++) {
432 var challenge = signChallenges[i];
433 var keyHandle = challenge['keyHandle'];
434 var challengeValue;
435 if (challenge.hasOwnProperty('challenge')) {
436 challengeValue = challenge['challenge'];
437 } else {
438 challengeValue = opt_defaultChallenge;
440 var challengeHash = challengeHashFn(keyHandle, challengeValue);
441 var appId;
442 if (challenge.hasOwnProperty('appId')) {
443 appId = challenge['appId'];
444 } else {
445 appId = opt_defaultAppId;
447 var encodedChallenge = {
448 'challengeHash': challengeHash,
449 'appIdHash': B64_encode(sha256HashOfString(appId)),
450 'keyHandle': keyHandle,
451 'version': (challenge['version'] || 'U2F_V1')
453 encodedSignChallenges.push(encodedChallenge);
456 return encodedSignChallenges;
460 * Makes a sign helper request from an array of challenges.
461 * @param {Array.<SignHelperChallenge>} challenges The sign challenges.
462 * @param {number=} opt_timeoutSeconds Timeout value.
463 * @param {string=} opt_logMsgUrl URL to log to.
464 * @return {SignHelperRequest} The sign helper request.
466 function makeSignHelperRequest(challenges, opt_timeoutSeconds, opt_logMsgUrl) {
467 var request = {
468 'type': 'sign_helper_request',
469 'signData': challenges,
470 'timeout': opt_timeoutSeconds || 0,
471 'timeoutSeconds': opt_timeoutSeconds || 0
473 if (opt_logMsgUrl !== undefined) {
474 request.logMsgUrl = opt_logMsgUrl;
476 return request;