Created
June 18, 2015 15:15
-
-
Save 19h/68f26c6e95d195bc3b89 to your computer and use it in GitHub Desktop.
U2F API for Chrome by Google Inc.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Copyright 2014 Google Inc. All rights reserved | |
// | |
// Use of this source code is governed by a BSD-style | |
// license that can be found in the LICENSE file or at | |
// https://developers.google.com/open-source/licenses/bsd | |
/** | |
* @fileoverview The U2F api. | |
*/ | |
'use strict'; | |
/** Namespace for the U2F api. | |
* @type {Object} | |
*/ | |
var u2f = u2f || {}; | |
/** | |
* The U2F extension id | |
* @type {string} | |
* @const | |
*/ | |
u2f.EXTENSION_ID = 'kmendfapggjehodndflmmgagdbamhnfd'; | |
/** | |
* Message types for messsages to/from the extension | |
* @const | |
* @enum {string} | |
*/ | |
u2f.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' | |
}; | |
/** | |
* Response status codes | |
* @const | |
* @enum {number} | |
*/ | |
u2f.ErrorCodes = { | |
'OK': 0, | |
'OTHER_ERROR': 1, | |
'BAD_REQUEST': 2, | |
'CONFIGURATION_UNSUPPORTED': 3, | |
'DEVICE_INELIGIBLE': 4, | |
'TIMEOUT': 5 | |
}; | |
/** | |
* A message type for registration requests | |
* @typedef {{ | |
* type: u2f.MessageTypes, | |
* signRequests: Array.<u2f.SignRequest>, | |
* registerRequests: ?Array.<u2f.RegisterRequest>, | |
* timeoutSeconds: ?number, | |
* requestId: ?number | |
* }} | |
*/ | |
u2f.Request; | |
/** | |
* A message for registration responses | |
* @typedef {{ | |
* type: u2f.MessageTypes, | |
* responseData: (u2f.Error | u2f.RegisterResponse | u2f.SignResponse), | |
* requestId: ?number | |
* }} | |
*/ | |
u2f.Response; | |
/** | |
* An error object for responses | |
* @typedef {{ | |
* errorCode: u2f.ErrorCodes, | |
* errorMessage: ?string | |
* }} | |
*/ | |
u2f.Error; | |
/** | |
* Data object for a single sign request. | |
* @typedef {{ | |
* version: string, | |
* challenge: string, | |
* keyHandle: string, | |
* appId: string | |
* }} | |
*/ | |
u2f.SignRequest; | |
/** | |
* Data object for a sign response. | |
* @typedef {{ | |
* keyHandle: string, | |
* signatureData: string, | |
* clientData: string | |
* }} | |
*/ | |
u2f.SignResponse; | |
/** | |
* Data object for a registration request. | |
* @typedef {{ | |
* version: string, | |
* challenge: string, | |
* appId: string | |
* }} | |
*/ | |
u2f.RegisterRequest; | |
/** | |
* Data object for a registration response. | |
* @typedef {{ | |
* registrationData: string, | |
* clientData: string | |
* }} | |
*/ | |
u2f.RegisterResponse; | |
// Low level MessagePort API support | |
/** | |
* Sets up a MessagePort to the U2F extension using the | |
* available mechanisms. | |
* @param {function((MessagePort|u2f.WrappedChromeRuntimePort_))} callback | |
*/ | |
u2f.getMessagePort = function(callback) { | |
if (typeof chrome != 'undefined' && chrome.runtime) { | |
// The actual message here does not matter, but we need to get a reply | |
// for the callback to run. Thus, send an empty signature request | |
// in order to get a failure response. | |
var msg = { | |
type: u2f.MessageTypes.U2F_SIGN_REQUEST, | |
signRequests: [] | |
}; | |
chrome.runtime.sendMessage(u2f.EXTENSION_ID, msg, function() { | |
if (!chrome.runtime.lastError) { | |
// We are on a whitelisted origin and can talk directly | |
// with the extension. | |
u2f.getChromeRuntimePort_(callback); | |
} else { | |
// chrome.runtime was available, but we couldn't message | |
// the extension directly, use iframe | |
u2f.getIframePort_(callback); | |
} | |
}); | |
} else { | |
// chrome.runtime was not available at all, which is normal | |
// when this origin doesn't have access to any extensions. | |
u2f.getIframePort_(callback); | |
} | |
}; | |
/** | |
* Connects directly to the extension via chrome.runtime.connect | |
* @param {function(u2f.WrappedChromeRuntimePort_)} callback | |
* @private | |
*/ | |
u2f.getChromeRuntimePort_ = function(callback) { | |
var port = chrome.runtime.connect(u2f.EXTENSION_ID, | |
{'includeTlsChannelId': true}); | |
setTimeout(function() { | |
callback(new u2f.WrappedChromeRuntimePort_(port)); | |
}, 0); | |
}; | |
/** | |
* A wrapper for chrome.runtime.Port that is compatible with MessagePort. | |
* @param {Port} port | |
* @constructor | |
* @private | |
*/ | |
u2f.WrappedChromeRuntimePort_ = function(port) { | |
this.port_ = port; | |
}; | |
/** | |
* Posts a message on the underlying channel. | |
* @param {Object} message | |
*/ | |
u2f.WrappedChromeRuntimePort_.prototype.postMessage = function(message) { | |
this.port_.postMessage(message); | |
}; | |
/** | |
* Emulates the HTML 5 addEventListener interface. Works only for the | |
* onmessage event, which is hooked up to the chrome.runtime.Port.onMessage. | |
* @param {string} eventName | |
* @param {function({data: Object})} handler | |
*/ | |
u2f.WrappedChromeRuntimePort_.prototype.addEventListener = | |
function(eventName, handler) { | |
var name = eventName.toLowerCase(); | |
if (name == 'message' || name == 'onmessage') { | |
this.port_.onMessage.addListener(function(message) { | |
// Emulate a minimal MessageEvent object | |
handler({'data': message}); | |
}); | |
} else { | |
console.error('WrappedChromeRuntimePort only supports onMessage'); | |
} | |
}; | |
/** | |
* Sets up an embedded trampoline iframe, sourced from the extension. | |
* @param {function(MessagePort)} callback | |
* @private | |
*/ | |
u2f.getIframePort_ = function(callback) { | |
// Create the iframe | |
var iframeOrigin = 'chrome-extension://' + u2f.EXTENSION_ID; | |
var iframe = document.createElement('iframe'); | |
iframe.src = iframeOrigin + '/u2f-comms.html'; | |
iframe.setAttribute('style', 'display:none'); | |
document.body.appendChild(iframe); | |
var channel = new MessageChannel(); | |
var ready = function(message) { | |
if (message.data == 'ready') { | |
channel.port1.removeEventListener('message', ready); | |
callback(channel.port1); | |
} else { | |
console.error('First event on iframe port was not "ready"'); | |
} | |
}; | |
channel.port1.addEventListener('message', ready); | |
channel.port1.start(); | |
iframe.addEventListener('load', function() { | |
// Deliver the port to the iframe and initialize | |
iframe.contentWindow.postMessage('init', iframeOrigin, [channel.port2]); | |
}); | |
}; | |
// High-level JS API | |
/** | |
* Default extension response timeout in seconds. | |
* @const | |
*/ | |
u2f.EXTENSION_TIMEOUT_SEC = 30; | |
/** | |
* A singleton instance for a MessagePort to the extension. | |
* @type {MessagePort|u2f.WrappedChromeRuntimePort_} | |
* @private | |
*/ | |
u2f.port_ = null; | |
/** | |
* Callbacks waiting for a port | |
* @type {Array.<function((MessagePort|u2f.WrappedChromeRuntimePort_))>} | |
* @private | |
*/ | |
u2f.waitingForPort_ = []; | |
/** | |
* A counter for requestIds. | |
* @type {number} | |
* @private | |
*/ | |
u2f.reqCounter_ = 0; | |
/** | |
* A map from requestIds to client callbacks | |
* @type {Object.<number,(function((u2f.Error|u2f.RegisterResponse)) | |
* |function((u2f.Error|u2f.SignResponse)))>} | |
* @private | |
*/ | |
u2f.callbackMap_ = {}; | |
/** | |
* Creates or retrieves the MessagePort singleton to use. | |
* @param {function((MessagePort|u2f.WrappedChromeRuntimePort_))} callback | |
* @private | |
*/ | |
u2f.getPortSingleton_ = function(callback) { | |
if (u2f.port_) { | |
callback(u2f.port_); | |
} else { | |
if (u2f.waitingForPort_.length == 0) { | |
u2f.getMessagePort(function(port) { | |
u2f.port_ = port; | |
u2f.port_.addEventListener('message', | |
/** @type {function(Event)} */ (u2f.responseHandler_)); | |
// Careful, here be async callbacks. Maybe. | |
while (u2f.waitingForPort_.length) | |
u2f.waitingForPort_.shift()(u2f.port_); | |
}); | |
} | |
u2f.waitingForPort_.push(callback); | |
} | |
}; | |
/** | |
* Handles response messages from the extension. | |
* @param {MessageEvent.<u2f.Response>} message | |
* @private | |
*/ | |
u2f.responseHandler_ = function(message) { | |
var response = message.data; | |
var reqId = response['requestId']; | |
if (!reqId || !u2f.callbackMap_[reqId]) { | |
console.error('Unknown or missing requestId in response.'); | |
return; | |
} | |
var cb = u2f.callbackMap_[reqId]; | |
delete u2f.callbackMap_[reqId]; | |
cb(response['responseData']); | |
}; | |
/** | |
* Dispatches an array of sign requests to available U2F tokens. | |
* @param {Array.<u2f.SignRequest>} signRequests | |
* @param {function((u2f.Error|u2f.SignResponse))} callback | |
* @param {number=} opt_timeoutSeconds | |
*/ | |
u2f.sign = function(signRequests, callback, opt_timeoutSeconds) { | |
u2f.getPortSingleton_(function(port) { | |
var reqId = ++u2f.reqCounter_; | |
u2f.callbackMap_[reqId] = callback; | |
var req = { | |
type: u2f.MessageTypes.U2F_SIGN_REQUEST, | |
signRequests: signRequests, | |
timeoutSeconds: (typeof opt_timeoutSeconds !== 'undefined' ? | |
opt_timeoutSeconds : u2f.EXTENSION_TIMEOUT_SEC), | |
requestId: reqId | |
}; | |
port.postMessage(req); | |
}); | |
}; | |
/** | |
* Dispatches register requests to available U2F tokens. An array of sign | |
* requests identifies already registered tokens. | |
* @param {Array.<u2f.RegisterRequest>} registerRequests | |
* @param {Array.<u2f.SignRequest>} signRequests | |
* @param {function((u2f.Error|u2f.RegisterResponse))} callback | |
* @param {number=} opt_timeoutSeconds | |
*/ | |
u2f.register = function(registerRequests, signRequests, | |
callback, opt_timeoutSeconds) { | |
u2f.getPortSingleton_(function(port) { | |
var reqId = ++u2f.reqCounter_; | |
u2f.callbackMap_[reqId] = callback; | |
var req = { | |
type: u2f.MessageTypes.U2F_REGISTER_REQUEST, | |
signRequests: signRequests, | |
registerRequests: registerRequests, | |
timeoutSeconds: (typeof opt_timeoutSeconds !== 'undefined' ? | |
opt_timeoutSeconds : u2f.EXTENSION_TIMEOUT_SEC), | |
requestId: reqId | |
}; | |
port.postMessage(req); | |
}); | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment