OLD | NEW |
1 // Copyright 2014 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 /** | 5 /** |
6 * @fileoverview Does common handling for requests coming from web pages and | 6 * @fileoverview Does common handling for requests coming from web pages and |
7 * routes them to the provided handler. | 7 * routes them to the provided handler. |
8 */ | 8 */ |
9 | 9 |
10 /** | 10 /** |
11 * Gets the scheme + origin from a web url. | 11 * Gets the scheme + origin from a web url. |
12 * @param {string} url Input url | 12 * @param {string} url Input url |
13 * @return {?string} Scheme and origin part if url parses | 13 * @return {?string} Scheme and origin part if url parses |
14 */ | 14 */ |
15 function getOriginFromUrl(url) { | 15 function getOriginFromUrl(url) { |
16 var re = new RegExp('^(https?://)[^/]*/?'); | 16 var re = new RegExp('^(https?://)[^/]*/?'); |
17 var originarray = re.exec(url); | 17 var originarray = re.exec(url); |
18 if (originarray == null) return originarray; | 18 if (originarray == null) return originarray; |
19 var origin = originarray[0]; | 19 var origin = originarray[0]; |
20 while (origin.charAt(origin.length - 1) == '/') { | 20 while (origin.charAt(origin.length - 1) == '/') { |
21 origin = origin.substring(0, origin.length - 1); | 21 origin = origin.substring(0, origin.length - 1); |
22 } | 22 } |
23 if (origin == 'http:' || origin == 'https:') | 23 if (origin == 'http:' || origin == 'https:') |
24 return null; | 24 return null; |
25 return origin; | 25 return origin; |
26 } | 26 } |
27 | 27 |
28 /** | 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. |
| 34 */ |
| 35 function isValidRegisteredKey(registeredKey, appIdRequired) { |
| 36 if (appIdRequired && !registeredKey.hasOwnProperty('appId')) { |
| 37 return false; |
| 38 } |
| 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; |
| 45 } |
| 46 } |
| 47 return true; |
| 48 } |
| 49 |
| 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. |
| 56 */ |
| 57 function isValidRegisteredKeyArray(registeredKeys, appIdRequired) { |
| 58 return registeredKeys.every(function(key) { |
| 59 return isValidRegisteredKey(key, appIdRequired); |
| 60 }); |
| 61 } |
| 62 |
| 63 /** |
29 * Returns whether the array of SignChallenges appears to be valid. | 64 * Returns whether the array of SignChallenges appears to be valid. |
30 * @param {Array.<SignChallenge>} signChallenges The array of sign challenges. | 65 * @param {Array.<SignChallenge>} signChallenges The array of sign challenges. |
| 66 * @param {boolean} appIdRequired Whether the appId property is required on |
| 67 * each challenge. |
31 * @return {boolean} Whether the array appears valid. | 68 * @return {boolean} Whether the array appears valid. |
32 */ | 69 */ |
33 function isValidSignChallengeArray(signChallenges) { | 70 function isValidSignChallengeArray(signChallenges, appIdRequired) { |
34 for (var i = 0; i < signChallenges.length; i++) { | 71 for (var i = 0; i < signChallenges.length; i++) { |
35 var incomingChallenge = signChallenges[i]; | 72 var incomingChallenge = signChallenges[i]; |
36 if (!incomingChallenge.hasOwnProperty('challenge')) | 73 if (!incomingChallenge.hasOwnProperty('challenge')) |
37 return false; | 74 return false; |
38 if (!incomingChallenge.hasOwnProperty('appId')) { | 75 if (!isValidRegisteredKey(incomingChallenge, appIdRequired)) { |
39 return false; | 76 return false; |
40 } | 77 } |
41 if (!incomingChallenge.hasOwnProperty('keyHandle')) | |
42 return false; | |
43 if (incomingChallenge['version']) { | |
44 if (incomingChallenge['version'] != 'U2F_V1' && | |
45 incomingChallenge['version'] != 'U2F_V2') { | |
46 return false; | |
47 } | |
48 } | |
49 } | 78 } |
50 return true; | 79 return true; |
51 } | 80 } |
52 | 81 |
53 /** Posts the log message to the log url. | 82 /** Posts the log message to the log url. |
54 * @param {string} logMsg the log message to post. | 83 * @param {string} logMsg the log message to post. |
55 * @param {string=} opt_logMsgUrl the url to post log messages to. | 84 * @param {string=} opt_logMsgUrl the url to post log messages to. |
56 */ | 85 */ |
57 function logMessage(logMsg, opt_logMsgUrl) { | 86 function logMessage(logMsg, opt_logMsgUrl) { |
58 console.log(UTIL_fmt('logMessage("' + logMsg + '")')); | 87 console.log(UTIL_fmt('logMessage("' + logMsg + '")')); |
59 | 88 |
60 if (!opt_logMsgUrl) { | 89 if (!opt_logMsgUrl) { |
61 return; | 90 return; |
62 } | 91 } |
63 // Image fetching is not allowed per packaged app CSP. | 92 // Image fetching is not allowed per packaged app CSP. |
64 // But video and audio is. | 93 // But video and audio is. |
65 var audio = new Audio(); | 94 var audio = new Audio(); |
66 audio.src = opt_logMsgUrl + logMsg; | 95 audio.src = opt_logMsgUrl + logMsg; |
67 } | 96 } |
68 | 97 |
69 /** | 98 /** |
| 99 * @param {Object} request Request object |
| 100 * @param {MessageSender} sender Sender frame |
| 101 * @param {Function} sendResponse Response callback |
| 102 * @return {?Closeable} Optional handler object that should be closed when port |
| 103 * closes |
| 104 */ |
| 105 function handleWebPageRequest(request, sender, sendResponse) { |
| 106 switch (request.type) { |
| 107 case GnubbyMsgTypes.ENROLL_WEB_REQUEST: |
| 108 return handleWebEnrollRequest(sender, request, sendResponse); |
| 109 |
| 110 case GnubbyMsgTypes.SIGN_WEB_REQUEST: |
| 111 return handleWebSignRequest(sender, request, sendResponse); |
| 112 |
| 113 case MessageTypes.U2F_REGISTER_REQUEST: |
| 114 return handleU2fEnrollRequest(sender, request, sendResponse); |
| 115 |
| 116 case MessageTypes.U2F_SIGN_REQUEST: |
| 117 return handleU2fSignRequest(sender, request, sendResponse); |
| 118 |
| 119 default: |
| 120 sendResponse( |
| 121 makeU2fErrorResponse(request, ErrorCodes.BAD_REQUEST, undefined, |
| 122 MessageTypes.U2F_REGISTER_RESPONSE)); |
| 123 return null; |
| 124 } |
| 125 } |
| 126 |
| 127 /** |
70 * Makes a response to a request. | 128 * Makes a response to a request. |
71 * @param {Object} request The request to make a response to. | 129 * @param {Object} request The request to make a response to. |
72 * @param {string} responseSuffix How to name the response's type. | 130 * @param {string} responseSuffix How to name the response's type. |
73 * @param {string=} opt_defaultType The default response type, if none is | 131 * @param {string=} opt_defaultType The default response type, if none is |
74 * present in the request. | 132 * present in the request. |
75 * @return {Object} The response object. | 133 * @return {Object} The response object. |
76 */ | 134 */ |
77 function makeResponseForRequest(request, responseSuffix, opt_defaultType) { | 135 function makeResponseForRequest(request, responseSuffix, opt_defaultType) { |
78 var type; | 136 var type; |
79 if (request && request.type) { | 137 if (request && request.type) { |
(...skipping 223 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
303 * @return {string} A string representation of the browser data object. | 361 * @return {string} A string representation of the browser data object. |
304 */ | 362 */ |
305 function makeSignBrowserData(serverChallenge, origin, opt_tlsChannelId) { | 363 function makeSignBrowserData(serverChallenge, origin, opt_tlsChannelId) { |
306 return makeBrowserData( | 364 return makeBrowserData( |
307 'navigator.id.getAssertion', serverChallenge, origin, opt_tlsChannelId); | 365 'navigator.id.getAssertion', serverChallenge, origin, opt_tlsChannelId); |
308 } | 366 } |
309 | 367 |
310 /** | 368 /** |
311 * Encodes the sign data as an array of sign helper challenges. | 369 * Encodes the sign data as an array of sign helper challenges. |
312 * @param {Array.<SignChallenge>} signChallenges The sign challenges to encode. | 370 * @param {Array.<SignChallenge>} signChallenges The sign challenges to encode. |
| 371 * @param {string=} opt_defaultAppId The app id to use for each challenge, if |
| 372 * the challenge contains none. |
313 * @param {function(string, string): string=} opt_challengeHashFunction | 373 * @param {function(string, string): string=} opt_challengeHashFunction |
314 * A function that produces, from a key handle and a raw challenge, a hash | 374 * A function that produces, from a key handle and a raw challenge, a hash |
315 * of the raw challenge. If none is provided, a default hash function is | 375 * of the raw challenge. If none is provided, a default hash function is |
316 * used. | 376 * used. |
317 * @return {!Array.<SignHelperChallenge>} The sign challenges, encoded. | 377 * @return {!Array.<SignHelperChallenge>} The sign challenges, encoded. |
318 */ | 378 */ |
319 function encodeSignChallenges(signChallenges, opt_challengeHashFunction) { | 379 function encodeSignChallenges(signChallenges, opt_defaultAppId, |
| 380 opt_challengeHashFunction) { |
320 function encodedSha256(keyHandle, challenge) { | 381 function encodedSha256(keyHandle, challenge) { |
321 return B64_encode(sha256HashOfString(challenge)); | 382 return B64_encode(sha256HashOfString(challenge)); |
322 } | 383 } |
323 var challengeHashFn = opt_challengeHashFunction || encodedSha256; | 384 var challengeHashFn = opt_challengeHashFunction || encodedSha256; |
324 var encodedSignChallenges = []; | 385 var encodedSignChallenges = []; |
325 if (signChallenges) { | 386 if (signChallenges) { |
326 for (var i = 0; i < signChallenges.length; i++) { | 387 for (var i = 0; i < signChallenges.length; i++) { |
327 var challenge = signChallenges[i]; | 388 var challenge = signChallenges[i]; |
328 var challengeHash = | 389 var challengeHash = |
329 challengeHashFn(challenge['keyHandle'], challenge['challenge']); | 390 challengeHashFn(challenge['keyHandle'], challenge['challenge']); |
| 391 var appId; |
| 392 if (challenge.hasOwnProperty('appId')) { |
| 393 appId = challenge['appId']; |
| 394 } else { |
| 395 appId = opt_defaultAppId; |
| 396 } |
330 var encodedChallenge = { | 397 var encodedChallenge = { |
331 'challengeHash': challengeHash, | 398 'challengeHash': challengeHash, |
332 'appIdHash': B64_encode(sha256HashOfString(challenge['appId'])), | 399 'appIdHash': B64_encode(sha256HashOfString(appId)), |
333 'keyHandle': challenge['keyHandle'], | 400 'keyHandle': challenge['keyHandle'], |
334 'version': (challenge['version'] || 'U2F_V1') | 401 'version': (challenge['version'] || 'U2F_V1') |
335 }; | 402 }; |
336 encodedSignChallenges.push(encodedChallenge); | 403 encodedSignChallenges.push(encodedChallenge); |
337 } | 404 } |
338 } | 405 } |
339 return encodedSignChallenges; | 406 return encodedSignChallenges; |
340 } | 407 } |
341 | 408 |
342 /** | 409 /** |
343 * Makes a sign helper request from an array of challenges. | 410 * Makes a sign helper request from an array of challenges. |
344 * @param {Array.<SignHelperChallenge>} challenges The sign challenges. | 411 * @param {Array.<SignHelperChallenge>} challenges The sign challenges. |
345 * @param {number=} opt_timeoutSeconds Timeout value. | 412 * @param {number=} opt_timeoutSeconds Timeout value. |
346 * @param {string=} opt_logMsgUrl URL to log to. | 413 * @param {string=} opt_logMsgUrl URL to log to. |
347 * @return {SignHelperRequest} The sign helper request. | 414 * @return {SignHelperRequest} The sign helper request. |
348 */ | 415 */ |
349 function makeSignHelperRequest(challenges, opt_timeoutSeconds, opt_logMsgUrl) { | 416 function makeSignHelperRequest(challenges, opt_timeoutSeconds, opt_logMsgUrl) { |
350 var request = { | 417 var request = { |
351 'type': 'sign_helper_request', | 418 'type': 'sign_helper_request', |
352 'signData': challenges, | 419 'signData': challenges, |
353 'timeout': opt_timeoutSeconds || 0, | 420 'timeout': opt_timeoutSeconds || 0, |
354 'timeoutSeconds': opt_timeoutSeconds || 0 | 421 'timeoutSeconds': opt_timeoutSeconds || 0 |
355 }; | 422 }; |
356 if (opt_logMsgUrl !== undefined) { | 423 if (opt_logMsgUrl !== undefined) { |
357 request.logMsgUrl = opt_logMsgUrl; | 424 request.logMsgUrl = opt_logMsgUrl; |
358 } | 425 } |
359 return request; | 426 return request; |
360 } | 427 } |
OLD | NEW |