blob: 62acfb0443e126c832738f4dcd6101faf45150ee [file] [log] [blame]
// 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();
}
});
});