Upstream version 7.36.149.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / resources / cryptotoken / webrequest.js
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 Does common handling for requests coming from web pages and
7  * routes them to the provided handler.
8  */
9
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
14  */
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);
22   }
23   if (origin == 'http:' || origin == 'https:')
24     return null;
25   return origin;
26 }
27
28 /**
29  * Parses the text as JSON and returns it as an array of strings.
30  * @param {string} text Input JSON
31  * @return {Array.<string>} Array of origins
32  */
33 function getOriginsFromJson(text) {
34   try {
35     var urls = JSON.parse(text);
36     var origins = [];
37     for (var i = 0, url; url = urls[i]; i++) {
38       var origin = getOriginFromUrl(url);
39       if (origin)
40         origins.push(origin);
41     }
42     return origins;
43   } catch (e) {
44     console.log(UTIL_fmt('could not parse ' + text));
45     return [];
46   }
47 }
48
49 /**
50  * Fetches the app id, and calls a callback with list of allowed origins for it.
51  * @param {string} appId the app id to fetch.
52  * @param {Function} cb called with a list of allowed origins for the app id.
53  */
54 function fetchAppId(appId, cb) {
55   var origin = getOriginFromUrl(appId);
56   if (!origin) {
57     cb(404, appId);
58     return;
59   }
60   var xhr = new XMLHttpRequest();
61   var origins = [];
62   xhr.open('GET', appId, true);
63   xhr.onloadend = function() {
64     if (xhr.status != 200) {
65       cb(xhr.status, appId);
66       return;
67     }
68     cb(xhr.status, appId, getOriginsFromJson(xhr.responseText));
69   };
70   xhr.send();
71 }
72
73 /**
74  * Retrieves a set of distinct app ids from the SignData.
75  * @param {SignData=} signData Input signature data
76  * @return {Array.<string>} array of distinct app ids.
77  */
78 function getDistinctAppIds(signData) {
79   var appIds = [];
80   if (!signData) {
81     return appIds;
82   }
83   for (var i = 0, request; request = signData[i]; i++) {
84     var appId = request['appId'];
85     if (appId && appIds.indexOf(appId) == -1) {
86       appIds.push(appId);
87     }
88   }
89   return appIds;
90 }
91
92 /**
93  * Reorganizes the requests from the SignData to an array of
94  * (appId, [Request]) tuples.
95  * @param {SignData} signData Input signature data
96  * @return {Array.<[string, Array.<Request>]>} array of
97  *     (appId, [Request]) tuples.
98  */
99 function requestsByAppId(signData) {
100   var requests = {};
101   var appIdOrder = {};
102   var orderToAppId = {};
103   var lastOrder = 0;
104   for (var i = 0, request; request = signData[i]; i++) {
105     var appId = request['appId'];
106     if (appId) {
107       if (!appIdOrder.hasOwnProperty(appId)) {
108         appIdOrder[appId] = lastOrder;
109         orderToAppId[lastOrder] = appId;
110         lastOrder++;
111       }
112       if (requests[appId]) {
113         requests[appId].push(request);
114       } else {
115         requests[appId] = [request];
116       }
117     }
118   }
119   var orderedRequests = [];
120   for (var order = 0; order < lastOrder; order++) {
121     appId = orderToAppId[order];
122     orderedRequests.push([appId, requests[appId]]);
123   }
124   return orderedRequests;
125 }
126
127 /**
128  * Fetches the allowed origins for an appId.
129  * @param {string} appId Application id
130  * @param {boolean} allowHttp Whether http is a valid scheme for an appId.
131  *     (This should be false except on test domains.)
132  * @param {function(number, !Array.<string>)} cb Called back with an HTTP
133  *     response code and a list of allowed origins for appId.
134  */
135 function fetchAllowedOriginsForAppId(appId, allowHttp, cb) {
136   var allowedOrigins = [];
137   if (!appId) {
138     cb(200, allowedOrigins);
139     return;
140   }
141   if (appId.indexOf('http://') == 0 && !allowHttp) {
142     console.log(UTIL_fmt('http app ids disallowed, ' + appId + ' requested'));
143     cb(200, allowedOrigins);
144     return;
145   }
146   // TODO: hack for old enrolled gnubbies, don't treat
147   // accounts.google.com/login.corp.google.com specially when cryptauth server
148   // stops reporting them as appId.
149   if (appId == 'https://accounts.google.com') {
150     allowedOrigins = ['https://login.corp.google.com'];
151     cb(200, allowedOrigins);
152     return;
153   }
154   if (appId == 'https://login.corp.google.com') {
155     allowedOrigins = ['https://accounts.google.com'];
156     cb(200, allowedOrigins);
157     return;
158   }
159   // Termination of this function relies in fetchAppId completing.
160   // (Not completing would be a bug in XMLHttpRequest.)
161   // TODO: provide a termination guarantee, e.g. with a timer?
162   fetchAppId(appId, function(rc, fetchedAppId, origins) {
163     if (rc != 200) {
164       console.log(UTIL_fmt('fetching ' + fetchedAppId + ' failed: ' + rc));
165       allowedOrigins = [];
166     } else {
167       allowedOrigins = origins;
168     }
169     cb(rc, allowedOrigins);
170   });
171 }
172
173 /**
174  * Checks whether an appId is valid for a given origin.
175  * @param {!string} appId Application id
176  * @param {!string} origin Origin
177  * @param {!Array.<string>} allowedOrigins the list of allowed origins for each
178  *    appId.
179  * @return {boolean} whether the appId is allowed for the origin.
180  */
181 function isValidAppIdForOrigin(appId, origin, allowedOrigins) {
182   if (!appId)
183     return false;
184   if (appId == origin) {
185     // trivially allowed
186     return true;
187   }
188   if (!allowedOrigins)
189     return false;
190   return allowedOrigins.indexOf(origin) >= 0;
191 }
192
193 /**
194  * Returns whether the signData object appears to be valid.
195  * @param {Array.<Object>} signData the signData object.
196  * @return {boolean} whether the object appears valid.
197  */
198 function isValidSignData(signData) {
199   for (var i = 0; i < signData.length; i++) {
200     var incomingChallenge = signData[i];
201     if (!incomingChallenge.hasOwnProperty('challenge'))
202       return false;
203     if (!incomingChallenge.hasOwnProperty('appId')) {
204       return false;
205     }
206     if (!incomingChallenge.hasOwnProperty('keyHandle'))
207       return false;
208     if (incomingChallenge['version']) {
209       if (incomingChallenge['version'] != 'U2F_V1' &&
210           incomingChallenge['version'] != 'U2F_V2') {
211         return false;
212       }
213     }
214   }
215   return true;
216 }
217
218 /** Posts the log message to the log url.
219  * @param {string} logMsg the log message to post.
220  * @param {string=} opt_logMsgUrl the url to post log messages to.
221  */
222 function logMessage(logMsg, opt_logMsgUrl) {
223   console.log(UTIL_fmt('logMessage("' + logMsg + '")'));
224
225   if (!opt_logMsgUrl) {
226     return;
227   }
228   // Image fetching is not allowed per packaged app CSP.
229   // But video and audio is.
230   var audio = new Audio();
231   audio.src = opt_logMsgUrl + logMsg;
232 }
233
234 /**
235  * Logs the result of fetching an appId.
236  * @param {!string} appId Application Id
237  * @param {number} millis elapsed time while fetching the appId.
238  * @param {Array.<string>} allowedOrigins the allowed origins retrieved.
239  * @param {string=} opt_logMsgUrl the url to post log messages to.
240  */
241 function logFetchAppIdResult(appId, millis, allowedOrigins, opt_logMsgUrl) {
242   var logMsg = 'log=fetchappid&appid=' + appId + '&millis=' + millis +
243       '&numorigins=' + allowedOrigins.length;
244   logMessage(logMsg, opt_logMsgUrl);
245 }
246
247 /**
248  * Logs a mismatch between an origin and an appId.
249  * @param {string} origin Origin
250  * @param {!string} appId Application id
251  * @param {string=} opt_logMsgUrl the url to post log messages to
252  */
253 function logInvalidOriginForAppId(origin, appId, opt_logMsgUrl) {
254   var logMsg = 'log=originrejected&origin=' + origin + '&appid=' + appId;
255   logMessage(logMsg, opt_logMsgUrl);
256 }
257
258 /**
259  * Formats response parameters as an object.
260  * @param {string} type type of the post message.
261  * @param {number} code status code of the operation.
262  * @param {Object=} responseData the response data of the operation.
263  * @return {Object} formatted response.
264  */
265 function formatWebPageResponse(type, code, responseData) {
266   var responseJsonObject = {};
267   responseJsonObject['type'] = type;
268   responseJsonObject['code'] = code;
269   if (responseData)
270     responseJsonObject['responseData'] = responseData;
271   return responseJsonObject;
272 }
273
274 /**
275  * @param {!string} string Input string
276  * @return {Array.<number>} SHA256 hash value of string.
277  */
278 function sha256HashOfString(string) {
279   var s = new SHA256();
280   s.update(UTIL_StringToBytes(string));
281   return s.digest();
282 }
283
284 /**
285  * Normalizes the TLS channel ID value:
286  * 1. Converts semantically empty values (undefined, null, 0) to the empty
287  *     string.
288  * 2. Converts valid JSON strings to a JS object.
289  * 3. Otherwise, returns the input value unmodified.
290  * @param {Object|string|undefined} opt_tlsChannelId TLS Channel id
291  * @return {Object|string} The normalized TLS channel ID value.
292  */
293 function tlsChannelIdValue(opt_tlsChannelId) {
294   if (!opt_tlsChannelId) {
295     // Case 1: Always set some value for  TLS channel ID, even if it's the empty
296     // string: this browser definitely supports them.
297     return '';
298   }
299   if (typeof opt_tlsChannelId === 'string') {
300     try {
301       var obj = JSON.parse(opt_tlsChannelId);
302       if (!obj) {
303         // Case 1: The string value 'null' parses as the Javascript object null,
304         // so return an empty string: the browser definitely supports TLS
305         // channel id.
306         return '';
307       }
308       // Case 2: return the value as a JS object.
309       return /** @type {Object} */ (obj);
310     } catch (e) {
311       console.warn('Unparseable TLS channel ID value ' + opt_tlsChannelId);
312       // Case 3: return the value unmodified.
313     }
314   }
315   return opt_tlsChannelId;
316 }
317
318 /**
319  * Creates a browser data object with the given values.
320  * @param {!string} type A string representing the "type" of this browser data
321  *     object.
322  * @param {!string} serverChallenge The server's challenge, as a base64-
323  *     encoded string.
324  * @param {!string} origin The server's origin, as seen by the browser.
325  * @param {Object|string|undefined} opt_tlsChannelId TLS Channel Id
326  * @return {string} A string representation of the browser data object.
327  */
328 function makeBrowserData(type, serverChallenge, origin, opt_tlsChannelId) {
329   var browserData = {
330     'typ' : type,
331     'challenge' : serverChallenge,
332     'origin' : origin
333   };
334   browserData['cid_pubkey'] = tlsChannelIdValue(opt_tlsChannelId);
335   return JSON.stringify(browserData);
336 }
337
338 /**
339  * Creates a browser data object for an enroll request with the given values.
340  * @param {!string} serverChallenge The server's challenge, as a base64-
341  *     encoded string.
342  * @param {!string} origin The server's origin, as seen by the browser.
343  * @param {Object|string|undefined} opt_tlsChannelId TLS Channel Id
344  * @return {string} A string representation of the browser data object.
345  */
346 function makeEnrollBrowserData(serverChallenge, origin, opt_tlsChannelId) {
347   return makeBrowserData(
348       'navigator.id.finishEnrollment', serverChallenge, origin,
349       opt_tlsChannelId);
350 }
351
352 /**
353  * Creates a browser data object for a sign request with the given values.
354  * @param {!string} serverChallenge The server's challenge, as a base64-
355  *     encoded string.
356  * @param {!string} origin The server's origin, as seen by the browser.
357  * @param {Object|string|undefined} opt_tlsChannelId TLS Channel Id
358  * @return {string} A string representation of the browser data object.
359  */
360 function makeSignBrowserData(serverChallenge, origin, opt_tlsChannelId) {
361   return makeBrowserData(
362       'navigator.id.getAssertion', serverChallenge, origin, opt_tlsChannelId);
363 }
364
365 /**
366  * @param {string} browserData Browser data as JSON
367  * @param {string} appId Application Id
368  * @param {string} encodedKeyHandle B64 encoded key handle
369  * @param {string=} version Protocol version
370  * @return {SignHelperChallenge} Challenge object
371  */
372 function makeChallenge(browserData, appId, encodedKeyHandle, version) {
373   var appIdHash = B64_encode(sha256HashOfString(appId));
374   var browserDataHash = B64_encode(sha256HashOfString(browserData));
375   var keyHandle = encodedKeyHandle;
376
377   var challenge = {
378     'challengeHash': browserDataHash,
379     'appIdHash': appIdHash,
380     'keyHandle': keyHandle
381   };
382   // Version is implicitly U2F_V1 if not specified.
383   challenge['version'] = (version || 'U2F_V1');
384   return challenge;
385 }