| // Copyright 2014 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| /** |
| * @fileoverview U2F gnubbyd background page |
| */ |
| |
| 'use strict'; |
| |
| // Singleton tracking available devices. |
| var gnubbies = new Gnubbies(); |
| llUsbGnubby.register(gnubbies); |
| // Only include HID support if it's available in this browser. |
| if (chrome.hid) { |
| llHidGnubby.register(gnubbies); |
| } |
| |
| var GNUBBY_FACTORY = new UsbGnubbyFactory(gnubbies); |
| var TIMER_FACTORY = new CountdownTimerFactory(); |
| var SIGN_HELPER_FACTORY = |
| new UsbSignHelperFactory(GNUBBY_FACTORY, TIMER_FACTORY); |
| var ENROLL_HELPER_FACTORY = |
| new UsbEnrollHelperFactory(GNUBBY_FACTORY, TIMER_FACTORY); |
| |
| /** |
| * Response status codes |
| * @const |
| * @enum {number} |
| */ |
| var ErrorCodes = { |
| 'OK': 0, |
| 'OTHER_ERROR': 1, |
| 'BAD_REQUEST': 2, |
| 'CONFIGURATION_UNSUPPORTED': 3, |
| 'DEVICE_INELIGIBLE': 4, |
| 'TIMEOUT': 5 |
| }; |
| |
| /** |
| * Message types for messsages to/from the extension |
| * @const |
| * @enum {string} |
| */ |
| var MessageTypes = { |
| U2F_REGISTER_REQUEST: 'u2f_register_request', |
| U2F_SIGN_REQUEST: 'u2f_sign_request', |
| U2F_REGISTER_RESPONSE: 'u2f_register_response', |
| U2F_SIGN_RESPONSE: 'u2f_sign_response' |
| }; |
| |
| /** |
| * Translates a U2F request into a Gnubbyd web request. |
| * @param {Object} request U2F Request to translate |
| * @return {Object} The old (gnubbyd) style web request |
| * @private |
| */ |
| function translateFromU2FRequest_(request) { |
| var newRequest = {}; |
| if (request['type'] == MessageTypes.U2F_REGISTER_REQUEST) { |
| newRequest['type'] = GnubbyMsgTypes.ENROLL_WEB_REQUEST; |
| } else if (request['type'] == MessageTypes.U2F_SIGN_REQUEST) { |
| newRequest['type'] = GnubbyMsgTypes.SIGN_WEB_REQUEST; |
| } else { |
| return null; |
| } |
| |
| if (request['signRequests']) { |
| newRequest['signData'] = request['signRequests']; |
| } |
| |
| if (request['registerRequests']) { |
| newRequest['enrollChallenges'] = request['registerRequests']; |
| } |
| |
| if (typeof request['timeoutSeconds'] !== 'undefined') |
| newRequest['requestId'] = request['requestId']; |
| |
| // requestId is handled by the message handlers below |
| |
| return newRequest; |
| } |
| |
| /** |
| * Translates a Gnubbyd response to a U2F response |
| * @param {Object} response The old (gnubbyd) style web response |
| * @return {Object} The corresponding U2F response |
| * @private |
| */ |
| function translateToU2FResponse_(response) { |
| var newResponse = {}; |
| if (response['type'] == GnubbyMsgTypes.ENROLL_WEB_REPLY) { |
| newResponse['type'] = MessageTypes.U2F_REGISTER_RESPONSE; |
| } else if (response['type'] == GnubbyMsgTypes.SIGN_WEB_REPLY) { |
| newResponse['type'] = MessageTypes.U2F_SIGN_RESPONSE; |
| } else { |
| return null; |
| } |
| |
| if (response['code'] == GnubbyCodeTypes.OK) { |
| var gnubbyResponseData = response['responseData']; |
| var u2fResponseData = {}; |
| u2fResponseData['clientData'] = gnubbyResponseData['browserData']; |
| if (gnubbyResponseData['keyHandle']) |
| u2fResponseData['keyHandle'] = gnubbyResponseData['keyHandle']; |
| if (gnubbyResponseData['signatureData']) |
| u2fResponseData['signatureData'] = gnubbyResponseData['signatureData']; |
| if (gnubbyResponseData['enrollData']) |
| u2fResponseData['registrationData'] = gnubbyResponseData['enrollData']; |
| newResponse['responseData'] = u2fResponseData; |
| } else { |
| var code; |
| switch (response['code']) { |
| case GnubbyCodeTypes.BAD_REQUEST: |
| code = ErrorCodes.BAD_REQUEST; |
| break; |
| |
| case GnubbyCodeTypes.ALREADY_ENROLLED: |
| case GnubbyCodeTypes.NONE_PLUGGED_ENROLLED: |
| case GnubbyCodeTypes.NO_DEVICES_ENROLLED: |
| code = ErrorCodes.DEVICE_INELIGIBLE; |
| break; |
| |
| case GnubbyCodeTypes.BAD_APP_ID: |
| console.error('Invalid appId'); |
| code = ErrorCodes.BAD_REQUEST; |
| break; |
| |
| case GnubbyCodeTypes.UNKNOWN_ERROR: |
| code = ErrorCodes.OTHER_ERROR; |
| break; |
| |
| case GnubbyCodeTypes.WAIT_TOUCH: |
| code = ErrorCodes.TIMEOUT; |
| break; |
| |
| case GnubbyCodeTypes.NO_GNUBBIES: |
| case GnubbyCodeTypes.BUSY: |
| console.warn('Ignoring a deprecated gnubbyd error', response); |
| return null; |
| } |
| |
| var error = {'errorCode': code}; |
| if (response['errorDetail']) |
| error['errorMessage'] = response['errorDetail']; |
| newResponse['responseData'] = error; |
| } |
| |
| // requestId is handled by the message handlers below |
| |
| return newResponse; |
| } |
| |
| /** |
| * @param {Object} request Request object |
| * @param {MessageSender} sender Sender frame |
| * @param {Function} sendResponse Response callback |
| * @return {?Closeable} Optional handler object that should be closed when port |
| * closes |
| */ |
| function handleWebPageRequest(request, sender, sendResponse) { |
| |
| var wrappedSendResponse = function(resp) { |
| console.log(JSON.stringify(resp)); |
| var u2fResponse = translateToU2FResponse_(resp); |
| if (!u2fResponse) { |
| console.log('Dropping gnubby response which is not compatible with U2F', |
| resp); |
| return; |
| } |
| sendResponse(u2fResponse); |
| }; |
| |
| switch (request.type) { |
| case MessageTypes.U2F_REGISTER_REQUEST: |
| return handleEnrollRequest(ENROLL_HELPER_FACTORY, sender, |
| translateFromU2FRequest_(request), wrappedSendResponse); |
| |
| case GnubbyMsgTypes.ENROLL_WEB_REQUEST: |
| return handleEnrollRequest(ENROLL_HELPER_FACTORY, sender, |
| request, sendResponse); |
| |
| case MessageTypes.U2F_SIGN_REQUEST: |
| return handleSignRequest(SIGN_HELPER_FACTORY, sender, |
| translateFromU2FRequest_(request), wrappedSendResponse); |
| |
| case GnubbyMsgTypes.SIGN_WEB_REQUEST: |
| return handleSignRequest(SIGN_HELPER_FACTORY, sender, |
| request, sendResponse); |
| |
| default: |
| var response = { |
| 'type': MessageTypes.U2F_REGISTER_RESPONSE, |
| 'error': { 'errorCode': ErrorCodes.BAD_REQUEST } |
| }; |
| sendResponse(response); |
| return null; |
| } |
| } |
| |
| // Listen to individual messages sent from (whitelisted) webpages via |
| // chrome.runtime.sendMessage |
| function messageHandlerExternal(request, sender, sendResponse) { |
| var closeable = handleWebPageRequest(request, sender, function(response) { |
| response['requestId'] = request['requestId']; |
| sendResponse(response); |
| if (closeable) { |
| closeable.close(); |
| } |
| }); |
| } |
| chrome.runtime.onMessageExternal.addListener(messageHandlerExternal); |
| |
| // Listen to direct connection events, and wire up a message handler on the port |
| chrome.runtime.onConnectExternal.addListener(function(port) { |
| var closeable; |
| port.onMessage.addListener(function(request) { |
| closeable = handleWebPageRequest(request, port.sender, |
| function(response) { |
| response['requestId'] = request['requestId']; |
| port.postMessage(response); |
| }); |
| }); |
| port.onDisconnect.addListener(function() { |
| if (closeable) { |
| closeable.close(); |
| } |
| }); |
| }); |