mirror of
https://github.com/bitwarden/web
synced 2025-12-19 17:53:22 +00:00
1398 lines
48 KiB
JavaScript
1398 lines
48 KiB
JavaScript
(function (i, s, o, g, r, a, m) {
|
|
i['GoogleAnalyticsObject'] = r; i[r] = i[r] || function () {
|
|
(i[r].q = i[r].q || []).push(arguments)
|
|
}, i[r].l = 1 * new Date(); a = s.createElement(o),
|
|
m = s.getElementsByTagName(o)[0]; a.async = 1; a.src = g; m.parentNode.insertBefore(a, m)
|
|
})(window, document, 'script', 'https://www.google-analytics.com/analytics.js', 'ga');
|
|
|
|
ga('create', 'UA-81915606-3', 'auto');
|
|
|
|
var AdminLTEOptions = {
|
|
controlSidebarOptions: {
|
|
selector: '#adminlte-fakeselector'
|
|
}
|
|
};
|
|
|
|
//Copyright 2014-2015 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
|
|
|
|
// ref: https://github.com/google/u2f-ref-code/blob/master/u2f-gae-demo/war/js/u2f-api.js
|
|
|
|
/**
|
|
* @fileoverview The U2F api.
|
|
*/
|
|
'use strict';
|
|
|
|
/**
|
|
* Modification:
|
|
* Wrap implementation so that we can exit if window.u2f is already supplied by the browser (see below).
|
|
*/
|
|
(function (root) {
|
|
/**
|
|
* Modification:
|
|
* Only continue load this library if window.u2f is not already supplied by the browser.
|
|
*/
|
|
var isFirefox = navigator.userAgent.indexOf('Firefox') !== -1 || navigator.userAgent.indexOf('Gecko/') !== -1;
|
|
var browserImplementsU2f = !!((typeof root.u2f !== 'undefined') && root.u2f.register);
|
|
|
|
if (isFirefox && browserImplementsU2f) {
|
|
root.u2f.isSupported = true;
|
|
return;
|
|
}
|
|
|
|
/**
|
|
* Namespace for the U2F api.
|
|
* @type {Object}
|
|
*/
|
|
var u2f = root.u2f || {};
|
|
|
|
/**
|
|
* Modification:
|
|
* Check if browser supports U2F API before this wrapper was added.
|
|
*/
|
|
u2f.isSupported = !!(((typeof u2f !== 'undefined') && u2f.register) || ((typeof chrome !== 'undefined') && chrome.runtime));
|
|
|
|
/**
|
|
* FIDO U2F Javascript API Version
|
|
* @number
|
|
*/
|
|
var js_api_version;
|
|
|
|
/**
|
|
* The U2F extension id
|
|
* @const {string}
|
|
*/
|
|
// The Chrome packaged app extension ID.
|
|
// Uncomment this if you want to deploy a server instance that uses
|
|
// the package Chrome app and does not require installing the U2F Chrome extension.
|
|
u2f.EXTENSION_ID = 'kmendfapggjehodndflmmgagdbamhnfd';
|
|
// The U2F Chrome extension ID.
|
|
// Uncomment this if you want to deploy a server instance that uses
|
|
// the U2F Chrome extension to authenticate.
|
|
// u2f.EXTENSION_ID = 'pfboblefjcgdjicmnffhdgionmgcdmne';
|
|
|
|
|
|
/**
|
|
* Message types for messsages to/from the extension
|
|
* @const
|
|
* @enum {string}
|
|
*/
|
|
u2f.MessageTypes = {
|
|
'U2F_REGISTER_REQUEST': 'u2f_register_request',
|
|
'U2F_REGISTER_RESPONSE': 'u2f_register_response',
|
|
'U2F_SIGN_REQUEST': 'u2f_sign_request',
|
|
'U2F_SIGN_RESPONSE': 'u2f_sign_response',
|
|
'U2F_GET_API_VERSION_REQUEST': 'u2f_get_api_version_request',
|
|
'U2F_GET_API_VERSION_RESPONSE': 'u2f_get_api_version_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 for registration requests
|
|
* @typedef {{
|
|
* type: u2f.MessageTypes,
|
|
* appId: ?string,
|
|
* timeoutSeconds: ?number,
|
|
* requestId: ?number
|
|
* }}
|
|
*/
|
|
u2f.U2fRequest;
|
|
|
|
|
|
/**
|
|
* A message for registration responses
|
|
* @typedef {{
|
|
* type: u2f.MessageTypes,
|
|
* responseData: (u2f.Error | u2f.RegisterResponse | u2f.SignResponse),
|
|
* requestId: ?number
|
|
* }}
|
|
*/
|
|
u2f.U2fResponse;
|
|
|
|
|
|
/**
|
|
* An error object for responses
|
|
* @typedef {{
|
|
* errorCode: u2f.ErrorCodes,
|
|
* errorMessage: ?string
|
|
* }}
|
|
*/
|
|
u2f.Error;
|
|
|
|
/**
|
|
* Data object for a single sign request.
|
|
* @typedef {enum {BLUETOOTH_RADIO, BLUETOOTH_LOW_ENERGY, USB, NFC}}
|
|
*/
|
|
u2f.Transport;
|
|
|
|
|
|
/**
|
|
* Data object for a single sign request.
|
|
* @typedef {Array<u2f.Transport>}
|
|
*/
|
|
u2f.Transports;
|
|
|
|
/**
|
|
* 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
|
|
* }}
|
|
*/
|
|
u2f.RegisterRequest;
|
|
|
|
|
|
/**
|
|
* Data object for a registration response.
|
|
* @typedef {{
|
|
* version: string,
|
|
* keyHandle: string,
|
|
* transports: Transports,
|
|
* appId: string
|
|
* }}
|
|
*/
|
|
u2f.RegisterResponse;
|
|
|
|
|
|
/**
|
|
* Data object for a registered key.
|
|
* @typedef {{
|
|
* version: string,
|
|
* keyHandle: string,
|
|
* transports: ?Transports,
|
|
* appId: ?string
|
|
* }}
|
|
*/
|
|
u2f.RegisteredKey;
|
|
|
|
|
|
/**
|
|
* Data object for a get API register response.
|
|
* @typedef {{
|
|
* js_api_version: number
|
|
* }}
|
|
*/
|
|
u2f.GetJsApiVersionResponse;
|
|
|
|
|
|
//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 if (u2f.isAndroidChrome_()) {
|
|
u2f.getAuthenticatorPort_(callback);
|
|
} else if (u2f.isIosChrome_()) {
|
|
u2f.getIosPort_(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);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Detect chrome running on android based on the browser's useragent.
|
|
* @private
|
|
*/
|
|
u2f.isAndroidChrome_ = function () {
|
|
var userAgent = navigator.userAgent;
|
|
return userAgent.indexOf('Chrome') != -1 &&
|
|
userAgent.indexOf('Android') != -1;
|
|
};
|
|
|
|
/**
|
|
* Detect chrome running on iOS based on the browser's platform.
|
|
* @private
|
|
*/
|
|
u2f.isIosChrome_ = function () {
|
|
return ["iPhone", "iPad", "iPod"].indexOf(navigator.platform) > -1;
|
|
};
|
|
|
|
/**
|
|
* 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);
|
|
};
|
|
|
|
/**
|
|
* Return a 'port' abstraction to the Authenticator app.
|
|
* @param {function(u2f.WrappedAuthenticatorPort_)} callback
|
|
* @private
|
|
*/
|
|
u2f.getAuthenticatorPort_ = function (callback) {
|
|
setTimeout(function () {
|
|
callback(new u2f.WrappedAuthenticatorPort_());
|
|
}, 0);
|
|
};
|
|
|
|
/**
|
|
* Return a 'port' abstraction to the iOS client app.
|
|
* @param {function(u2f.WrappedIosPort_)} callback
|
|
* @private
|
|
*/
|
|
u2f.getIosPort_ = function (callback) {
|
|
setTimeout(function () {
|
|
callback(new u2f.WrappedIosPort_());
|
|
}, 0);
|
|
};
|
|
|
|
/**
|
|
* A wrapper for chrome.runtime.Port that is compatible with MessagePort.
|
|
* @param {Port} port
|
|
* @constructor
|
|
* @private
|
|
*/
|
|
u2f.WrappedChromeRuntimePort_ = function (port) {
|
|
this.port_ = port;
|
|
};
|
|
|
|
/**
|
|
* Format and return a sign request compliant with the JS API version supported by the extension.
|
|
* @param {Array<u2f.SignRequest>} signRequests
|
|
* @param {number} timeoutSeconds
|
|
* @param {number} reqId
|
|
* @return {Object}
|
|
*/
|
|
u2f.formatSignRequest_ =
|
|
function (appId, challenge, registeredKeys, timeoutSeconds, reqId) {
|
|
if (js_api_version === undefined || js_api_version < 1.1) {
|
|
// Adapt request to the 1.0 JS API
|
|
var signRequests = [];
|
|
for (var i = 0; i < registeredKeys.length; i++) {
|
|
signRequests[i] = {
|
|
version: registeredKeys[i].version,
|
|
challenge: challenge,
|
|
keyHandle: registeredKeys[i].keyHandle,
|
|
appId: appId
|
|
};
|
|
}
|
|
return {
|
|
type: u2f.MessageTypes.U2F_SIGN_REQUEST,
|
|
signRequests: signRequests,
|
|
timeoutSeconds: timeoutSeconds,
|
|
requestId: reqId
|
|
};
|
|
}
|
|
// JS 1.1 API
|
|
return {
|
|
type: u2f.MessageTypes.U2F_SIGN_REQUEST,
|
|
appId: appId,
|
|
challenge: challenge,
|
|
registeredKeys: registeredKeys,
|
|
timeoutSeconds: timeoutSeconds,
|
|
requestId: reqId
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Format and return a register request compliant with the JS API version supported by the extension..
|
|
* @param {Array<u2f.SignRequest>} signRequests
|
|
* @param {Array<u2f.RegisterRequest>} signRequests
|
|
* @param {number} timeoutSeconds
|
|
* @param {number} reqId
|
|
* @return {Object}
|
|
*/
|
|
u2f.formatRegisterRequest_ =
|
|
function (appId, registeredKeys, registerRequests, timeoutSeconds, reqId) {
|
|
if (js_api_version === undefined || js_api_version < 1.1) {
|
|
// Adapt request to the 1.0 JS API
|
|
for (var i = 0; i < registerRequests.length; i++) {
|
|
registerRequests[i].appId = appId;
|
|
}
|
|
var signRequests = [];
|
|
for (var i = 0; i < registeredKeys.length; i++) {
|
|
signRequests[i] = {
|
|
version: registeredKeys[i].version,
|
|
challenge: registerRequests[0],
|
|
keyHandle: registeredKeys[i].keyHandle,
|
|
appId: appId
|
|
};
|
|
}
|
|
return {
|
|
type: u2f.MessageTypes.U2F_REGISTER_REQUEST,
|
|
signRequests: signRequests,
|
|
registerRequests: registerRequests,
|
|
timeoutSeconds: timeoutSeconds,
|
|
requestId: reqId
|
|
};
|
|
}
|
|
// JS 1.1 API
|
|
return {
|
|
type: u2f.MessageTypes.U2F_REGISTER_REQUEST,
|
|
appId: appId,
|
|
registerRequests: registerRequests,
|
|
registeredKeys: registeredKeys,
|
|
timeoutSeconds: timeoutSeconds,
|
|
requestId: reqId
|
|
};
|
|
};
|
|
|
|
|
|
/**
|
|
* 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');
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Wrap the Authenticator app with a MessagePort interface.
|
|
* @constructor
|
|
* @private
|
|
*/
|
|
u2f.WrappedAuthenticatorPort_ = function () {
|
|
this.requestId_ = -1;
|
|
this.requestObject_ = null;
|
|
}
|
|
|
|
/**
|
|
* Launch the Authenticator intent.
|
|
* @param {Object} message
|
|
*/
|
|
u2f.WrappedAuthenticatorPort_.prototype.postMessage = function (message) {
|
|
var intentUrl =
|
|
u2f.WrappedAuthenticatorPort_.INTENT_URL_BASE_ +
|
|
';S.request=' + encodeURIComponent(JSON.stringify(message)) +
|
|
';end';
|
|
document.location = intentUrl;
|
|
};
|
|
|
|
/**
|
|
* Tells what type of port this is.
|
|
* @return {String} port type
|
|
*/
|
|
u2f.WrappedAuthenticatorPort_.prototype.getPortType = function () {
|
|
return "WrappedAuthenticatorPort_";
|
|
};
|
|
|
|
|
|
/**
|
|
* Emulates the HTML 5 addEventListener interface.
|
|
* @param {string} eventName
|
|
* @param {function({data: Object})} handler
|
|
*/
|
|
u2f.WrappedAuthenticatorPort_.prototype.addEventListener = function (eventName, handler) {
|
|
var name = eventName.toLowerCase();
|
|
if (name == 'message') {
|
|
var self = this;
|
|
/* Register a callback to that executes when
|
|
* chrome injects the response. */
|
|
window.addEventListener(
|
|
'message', self.onRequestUpdate_.bind(self, handler), false);
|
|
} else {
|
|
console.error('WrappedAuthenticatorPort only supports message');
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Callback invoked when a response is received from the Authenticator.
|
|
* @param function({data: Object}) callback
|
|
* @param {Object} message message Object
|
|
*/
|
|
u2f.WrappedAuthenticatorPort_.prototype.onRequestUpdate_ =
|
|
function (callback, message) {
|
|
var messageObject = JSON.parse(message.data);
|
|
var intentUrl = messageObject['intentURL'];
|
|
|
|
var errorCode = messageObject['errorCode'];
|
|
var responseObject = null;
|
|
if (messageObject.hasOwnProperty('data')) {
|
|
responseObject = /** @type {Object} */ (
|
|
JSON.parse(messageObject['data']));
|
|
}
|
|
|
|
callback({ 'data': responseObject });
|
|
};
|
|
|
|
/**
|
|
* Base URL for intents to Authenticator.
|
|
* @const
|
|
* @private
|
|
*/
|
|
u2f.WrappedAuthenticatorPort_.INTENT_URL_BASE_ =
|
|
'intent:#Intent;action=com.google.android.apps.authenticator.AUTHENTICATE';
|
|
|
|
/**
|
|
* Wrap the iOS client app with a MessagePort interface.
|
|
* @constructor
|
|
* @private
|
|
*/
|
|
u2f.WrappedIosPort_ = function () { };
|
|
|
|
/**
|
|
* Launch the iOS client app request
|
|
* @param {Object} message
|
|
*/
|
|
u2f.WrappedIosPort_.prototype.postMessage = function (message) {
|
|
var str = JSON.stringify(message);
|
|
var url = "u2f://auth?" + encodeURI(str);
|
|
location.replace(url);
|
|
};
|
|
|
|
/**
|
|
* Tells what type of port this is.
|
|
* @return {String} port type
|
|
*/
|
|
u2f.WrappedIosPort_.prototype.getPortType = function () {
|
|
return "WrappedIosPort_";
|
|
};
|
|
|
|
/**
|
|
* Emulates the HTML 5 addEventListener interface.
|
|
* @param {string} eventName
|
|
* @param {function({data: Object})} handler
|
|
*/
|
|
u2f.WrappedIosPort_.prototype.addEventListener = function (eventName, handler) {
|
|
var name = eventName.toLowerCase();
|
|
if (name !== 'message') {
|
|
console.error('WrappedIosPort only supports message');
|
|
}
|
|
};
|
|
|
|
/**
|
|
* 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.
|
|
* If the JS API version supported by the extension is unknown, it first sends a
|
|
* message to the extension to find out the supported API version and then it sends
|
|
* the sign request.
|
|
* @param {string=} appId
|
|
* @param {string=} challenge
|
|
* @param {Array<u2f.RegisteredKey>} registeredKeys
|
|
* @param {function((u2f.Error|u2f.SignResponse))} callback
|
|
* @param {number=} opt_timeoutSeconds
|
|
*/
|
|
u2f.sign = function (appId, challenge, registeredKeys, callback, opt_timeoutSeconds) {
|
|
if (js_api_version === undefined) {
|
|
// Send a message to get the extension to JS API version, then send the actual sign request.
|
|
u2f.getApiVersion(
|
|
function (response) {
|
|
js_api_version = response['js_api_version'] === undefined ? 0 : response['js_api_version'];
|
|
console.log("Extension JS API Version: ", js_api_version);
|
|
u2f.sendSignRequest(appId, challenge, registeredKeys, callback, opt_timeoutSeconds);
|
|
});
|
|
} else {
|
|
// We know the JS API version. Send the actual sign request in the supported API version.
|
|
u2f.sendSignRequest(appId, challenge, registeredKeys, callback, opt_timeoutSeconds);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Dispatches an array of sign requests to available U2F tokens.
|
|
* @param {string=} appId
|
|
* @param {string=} challenge
|
|
* @param {Array<u2f.RegisteredKey>} registeredKeys
|
|
* @param {function((u2f.Error|u2f.SignResponse))} callback
|
|
* @param {number=} opt_timeoutSeconds
|
|
*/
|
|
u2f.sendSignRequest = function (appId, challenge, registeredKeys, callback, opt_timeoutSeconds) {
|
|
u2f.getPortSingleton_(function (port) {
|
|
var reqId = ++u2f.reqCounter_;
|
|
u2f.callbackMap_[reqId] = callback;
|
|
var timeoutSeconds = (typeof opt_timeoutSeconds !== 'undefined' ?
|
|
opt_timeoutSeconds : u2f.EXTENSION_TIMEOUT_SEC);
|
|
var req = u2f.formatSignRequest_(appId, challenge, registeredKeys, timeoutSeconds, reqId);
|
|
port.postMessage(req);
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Dispatches register requests to available U2F tokens. An array of sign
|
|
* requests identifies already registered tokens.
|
|
* If the JS API version supported by the extension is unknown, it first sends a
|
|
* message to the extension to find out the supported API version and then it sends
|
|
* the register request.
|
|
* @param {string=} appId
|
|
* @param {Array<u2f.RegisterRequest>} registerRequests
|
|
* @param {Array<u2f.RegisteredKey>} registeredKeys
|
|
* @param {function((u2f.Error|u2f.RegisterResponse))} callback
|
|
* @param {number=} opt_timeoutSeconds
|
|
*/
|
|
u2f.register = function (appId, registerRequests, registeredKeys, callback, opt_timeoutSeconds) {
|
|
if (js_api_version === undefined) {
|
|
// Send a message to get the extension to JS API version, then send the actual register request.
|
|
u2f.getApiVersion(
|
|
function (response) {
|
|
js_api_version = response['js_api_version'] === undefined ? 0 : response['js_api_version'];
|
|
console.log("Extension JS API Version: ", js_api_version);
|
|
u2f.sendRegisterRequest(appId, registerRequests, registeredKeys,
|
|
callback, opt_timeoutSeconds);
|
|
});
|
|
} else {
|
|
// We know the JS API version. Send the actual register request in the supported API version.
|
|
u2f.sendRegisterRequest(appId, registerRequests, registeredKeys,
|
|
callback, opt_timeoutSeconds);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Dispatches register requests to available U2F tokens. An array of sign
|
|
* requests identifies already registered tokens.
|
|
* @param {string=} appId
|
|
* @param {Array<u2f.RegisterRequest>} registerRequests
|
|
* @param {Array<u2f.RegisteredKey>} registeredKeys
|
|
* @param {function((u2f.Error|u2f.RegisterResponse))} callback
|
|
* @param {number=} opt_timeoutSeconds
|
|
*/
|
|
u2f.sendRegisterRequest = function (appId, registerRequests, registeredKeys, callback, opt_timeoutSeconds) {
|
|
u2f.getPortSingleton_(function (port) {
|
|
var reqId = ++u2f.reqCounter_;
|
|
u2f.callbackMap_[reqId] = callback;
|
|
var timeoutSeconds = (typeof opt_timeoutSeconds !== 'undefined' ?
|
|
opt_timeoutSeconds : u2f.EXTENSION_TIMEOUT_SEC);
|
|
var req = u2f.formatRegisterRequest_(
|
|
appId, registeredKeys, registerRequests, timeoutSeconds, reqId);
|
|
port.postMessage(req);
|
|
});
|
|
};
|
|
|
|
|
|
/**
|
|
* Dispatches a message to the extension to find out the supported
|
|
* JS API version.
|
|
* If the user is on a mobile phone and is thus using Google Authenticator instead
|
|
* of the Chrome extension, don't send the request and simply return 0.
|
|
* @param {function((u2f.Error|u2f.GetJsApiVersionResponse))} callback
|
|
* @param {number=} opt_timeoutSeconds
|
|
*/
|
|
u2f.getApiVersion = function (callback, opt_timeoutSeconds) {
|
|
u2f.getPortSingleton_(function (port) {
|
|
// If we are using Android Google Authenticator or iOS client app,
|
|
// do not fire an intent to ask which JS API version to use.
|
|
if (port.getPortType) {
|
|
var apiVersion;
|
|
switch (port.getPortType()) {
|
|
case 'WrappedIosPort_':
|
|
case 'WrappedAuthenticatorPort_':
|
|
apiVersion = 1.1;
|
|
break;
|
|
|
|
default:
|
|
apiVersion = 0;
|
|
break;
|
|
}
|
|
callback({ 'js_api_version': apiVersion });
|
|
return;
|
|
}
|
|
var reqId = ++u2f.reqCounter_;
|
|
u2f.callbackMap_[reqId] = callback;
|
|
var req = {
|
|
type: u2f.MessageTypes.U2F_GET_API_VERSION_REQUEST,
|
|
timeoutSeconds: (typeof opt_timeoutSeconds !== 'undefined' ?
|
|
opt_timeoutSeconds : u2f.EXTENSION_TIMEOUT_SEC),
|
|
requestId: reqId
|
|
};
|
|
port.postMessage(req);
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Modification:
|
|
* Assign u2f back to window (root) scope.
|
|
*/
|
|
root.u2f = u2f;
|
|
}(this));
|
|
|
|
/**
|
|
* @file Web Cryptography API shim
|
|
* @author Artem S Vybornov <vybornov@gmail.com>
|
|
* @license MIT
|
|
*/
|
|
!function (global) {
|
|
'use strict';
|
|
|
|
// We are using an angular promise polyfill which is loaded after this script
|
|
//if (typeof Promise !== 'function')
|
|
// throw "Promise support required";
|
|
|
|
var _crypto = global.crypto || global.msCrypto;
|
|
if (!_crypto) return;
|
|
|
|
var _subtle = _crypto.subtle || _crypto.webkitSubtle;
|
|
if (!_subtle) return;
|
|
|
|
var _Crypto = global.Crypto || _crypto.constructor || Object,
|
|
_SubtleCrypto = global.SubtleCrypto || _subtle.constructor || Object,
|
|
_CryptoKey = global.CryptoKey || global.Key || Object;
|
|
|
|
var isIE = !!global.msCrypto,
|
|
// ref PR: https://github.com/vibornoff/webcrypto-shim/pull/15
|
|
isWebkit = !_crypto.subtle && !!_crypto.webkitSubtle;
|
|
if (!isIE && !isWebkit) return;
|
|
|
|
// Added
|
|
global.cryptoShimmed = true;
|
|
|
|
function s2a(s) {
|
|
return btoa(s).replace(/\=+$/, '').replace(/\+/g, '-').replace(/\//g, '_');
|
|
}
|
|
|
|
function a2s(s) {
|
|
s += '===', s = s.slice(0, -s.length % 4);
|
|
return atob(s.replace(/-/g, '+').replace(/_/g, '/'));
|
|
}
|
|
|
|
function s2b(s) {
|
|
var b = new Uint8Array(s.length);
|
|
for (var i = 0; i < s.length; i++) b[i] = s.charCodeAt(i);
|
|
return b;
|
|
}
|
|
|
|
function b2s(b) {
|
|
if (b instanceof ArrayBuffer) b = new Uint8Array(b);
|
|
return String.fromCharCode.apply(String, b);
|
|
}
|
|
|
|
function alg(a) {
|
|
var r = { 'name': (a.name || a || '').toUpperCase().replace('V', 'v') };
|
|
switch (r.name) {
|
|
case 'SHA-1':
|
|
case 'SHA-256':
|
|
case 'SHA-384':
|
|
case 'SHA-512':
|
|
break;
|
|
case 'AES-CBC':
|
|
case 'AES-GCM':
|
|
case 'AES-KW':
|
|
if (a.length) r['length'] = a.length;
|
|
break;
|
|
case 'HMAC':
|
|
if (a.hash) r['hash'] = alg(a.hash);
|
|
if (a.length) r['length'] = a.length;
|
|
break;
|
|
case 'RSAES-PKCS1-v1_5':
|
|
if (a.publicExponent) r['publicExponent'] = new Uint8Array(a.publicExponent);
|
|
if (a.modulusLength) r['modulusLength'] = a.modulusLength;
|
|
break;
|
|
case 'RSASSA-PKCS1-v1_5':
|
|
case 'RSA-OAEP':
|
|
if (a.hash) r['hash'] = alg(a.hash);
|
|
if (a.publicExponent) r['publicExponent'] = new Uint8Array(a.publicExponent);
|
|
if (a.modulusLength) r['modulusLength'] = a.modulusLength;
|
|
break;
|
|
default:
|
|
throw new SyntaxError("Bad algorithm name");
|
|
}
|
|
return r;
|
|
};
|
|
|
|
function jwkAlg(a) {
|
|
return {
|
|
'HMAC': {
|
|
'SHA-1': 'HS1',
|
|
'SHA-256': 'HS256',
|
|
'SHA-384': 'HS384',
|
|
'SHA-512': 'HS512',
|
|
},
|
|
'RSASSA-PKCS1-v1_5': {
|
|
'SHA-1': 'RS1',
|
|
'SHA-256': 'RS256',
|
|
'SHA-384': 'RS384',
|
|
'SHA-512': 'RS512',
|
|
},
|
|
'RSAES-PKCS1-v1_5': {
|
|
'': 'RSA1_5',
|
|
},
|
|
'RSA-OAEP': {
|
|
'SHA-1': 'RSA-OAEP',
|
|
'SHA-256': 'RSA-OAEP-256',
|
|
},
|
|
'AES-KW': {
|
|
'128': 'A128KW',
|
|
'192': 'A192KW',
|
|
'256': 'A256KW',
|
|
},
|
|
'AES-GCM': {
|
|
'128': 'A128GCM',
|
|
'192': 'A192GCM',
|
|
'256': 'A256GCM',
|
|
},
|
|
'AES-CBC': {
|
|
'128': 'A128CBC',
|
|
'192': 'A192CBC',
|
|
'256': 'A256CBC',
|
|
},
|
|
}[a.name][(a.hash || {}).name || a.length || ''];
|
|
}
|
|
|
|
function b2jwk(k) {
|
|
if (k instanceof ArrayBuffer || k instanceof Uint8Array) k = JSON.parse(decodeURIComponent(escape(b2s(k))));
|
|
var jwk = { 'kty': k.kty, 'alg': k.alg, 'ext': k.ext || k.extractable };
|
|
switch (jwk.kty) {
|
|
case 'oct':
|
|
jwk.k = k.k;
|
|
case 'RSA':
|
|
['n', 'e', 'd', 'p', 'q', 'dp', 'dq', 'qi', 'oth'].forEach(function (x) { if (x in k) jwk[x] = k[x] });
|
|
break;
|
|
default:
|
|
throw new TypeError("Unsupported key type");
|
|
}
|
|
return jwk;
|
|
}
|
|
|
|
function jwk2b(k) {
|
|
var jwk = b2jwk(k);
|
|
if (isIE) jwk['extractable'] = jwk.ext, delete jwk.ext;
|
|
return s2b(unescape(encodeURIComponent(JSON.stringify(jwk)))).buffer;
|
|
}
|
|
|
|
function pkcs2jwk(k) {
|
|
var info = b2der(k), prv = false;
|
|
if (info.length > 2) prv = true, info.shift(); // remove version from PKCS#8 PrivateKeyInfo structure
|
|
var jwk = { 'ext': true };
|
|
switch (info[0][0]) {
|
|
case '1.2.840.113549.1.1.1':
|
|
var rsaComp = ['n', 'e', 'd', 'p', 'q', 'dp', 'dq', 'qi'],
|
|
rsaKey = b2der(info[1]);
|
|
if (prv) rsaKey.shift(); // remove version from PKCS#1 RSAPrivateKey structure
|
|
for (var i = 0; i < rsaKey.length; i++) {
|
|
if (!rsaKey[i][0]) rsaKey[i] = rsaKey[i].subarray(1);
|
|
jwk[rsaComp[i]] = s2a(b2s(rsaKey[i]));
|
|
}
|
|
jwk['kty'] = 'RSA';
|
|
break;
|
|
default:
|
|
throw new TypeError("Unsupported key type");
|
|
}
|
|
return jwk;
|
|
}
|
|
|
|
function jwk2pkcs(k) {
|
|
var key, info = [['', null]], prv = false;
|
|
switch (k.kty) {
|
|
case 'RSA':
|
|
var rsaComp = ['n', 'e', 'd', 'p', 'q', 'dp', 'dq', 'qi'],
|
|
rsaKey = [];
|
|
for (var i = 0; i < rsaComp.length; i++) {
|
|
if (!(rsaComp[i] in k)) break;
|
|
var b = rsaKey[i] = s2b(a2s(k[rsaComp[i]]));
|
|
if (b[0] & 0x80) rsaKey[i] = new Uint8Array(b.length + 1), rsaKey[i].set(b, 1);
|
|
}
|
|
if (rsaKey.length > 2) prv = true, rsaKey.unshift(new Uint8Array([0])); // add version to PKCS#1 RSAPrivateKey structure
|
|
info[0][0] = '1.2.840.113549.1.1.1';
|
|
key = rsaKey;
|
|
break;
|
|
default:
|
|
throw new TypeError("Unsupported key type");
|
|
}
|
|
info.push(new Uint8Array(der2b(key)).buffer);
|
|
if (!prv) info[1] = { 'tag': 0x03, 'value': info[1] };
|
|
else info.unshift(new Uint8Array([0])); // add version to PKCS#8 PrivateKeyInfo structure
|
|
return new Uint8Array(der2b(info)).buffer;
|
|
}
|
|
|
|
var oid2str = { 'KoZIhvcNAQEB': '1.2.840.113549.1.1.1' },
|
|
str2oid = { '1.2.840.113549.1.1.1': 'KoZIhvcNAQEB' };
|
|
|
|
function b2der(buf, ctx) {
|
|
if (buf instanceof ArrayBuffer) buf = new Uint8Array(buf);
|
|
if (!ctx) ctx = { pos: 0, end: buf.length };
|
|
|
|
if (ctx.end - ctx.pos < 2 || ctx.end > buf.length) throw new RangeError("Malformed DER");
|
|
|
|
var tag = buf[ctx.pos++],
|
|
len = buf[ctx.pos++];
|
|
|
|
if (len >= 0x80) {
|
|
len &= 0x7f;
|
|
if (ctx.end - ctx.pos < len) throw new RangeError("Malformed DER");
|
|
for (var xlen = 0; len--;) xlen <<= 8, xlen |= buf[ctx.pos++];
|
|
len = xlen;
|
|
}
|
|
|
|
if (ctx.end - ctx.pos < len) throw new RangeError("Malformed DER");
|
|
|
|
var rv;
|
|
|
|
switch (tag) {
|
|
case 0x02: // Universal Primitive INTEGER
|
|
rv = buf.subarray(ctx.pos, ctx.pos += len);
|
|
break;
|
|
case 0x03: // Universal Primitive BIT STRING
|
|
if (buf[ctx.pos++]) throw new Error("Unsupported bit string");
|
|
len--;
|
|
case 0x04: // Universal Primitive OCTET STRING
|
|
rv = new Uint8Array(buf.subarray(ctx.pos, ctx.pos += len)).buffer;
|
|
break;
|
|
case 0x05: // Universal Primitive NULL
|
|
rv = null;
|
|
break;
|
|
case 0x06: // Universal Primitive OBJECT IDENTIFIER
|
|
var oid = btoa(b2s(buf.subarray(ctx.pos, ctx.pos += len)));
|
|
if (!(oid in oid2str)) throw new Error("Unsupported OBJECT ID " + oid);
|
|
rv = oid2str[oid];
|
|
break;
|
|
case 0x30: // Universal Constructed SEQUENCE
|
|
rv = [];
|
|
for (var end = ctx.pos + len; ctx.pos < end;) rv.push(b2der(buf, ctx));
|
|
break;
|
|
default:
|
|
throw new Error("Unsupported DER tag 0x" + tag.toString(16));
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
function der2b(val, buf) {
|
|
if (!buf) buf = [];
|
|
|
|
var tag = 0, len = 0,
|
|
pos = buf.length + 2;
|
|
|
|
buf.push(0, 0); // placeholder
|
|
|
|
if (val instanceof Uint8Array) { // Universal Primitive INTEGER
|
|
tag = 0x02, len = val.length;
|
|
for (var i = 0; i < len; i++) buf.push(val[i]);
|
|
}
|
|
else if (val instanceof ArrayBuffer) { // Universal Primitive OCTET STRING
|
|
tag = 0x04, len = val.byteLength, val = new Uint8Array(val);
|
|
for (var i = 0; i < len; i++) buf.push(val[i]);
|
|
}
|
|
else if (val === null) { // Universal Primitive NULL
|
|
tag = 0x05, len = 0;
|
|
}
|
|
else if (typeof val === 'string' && val in str2oid) { // Universal Primitive OBJECT IDENTIFIER
|
|
var oid = s2b(atob(str2oid[val]));
|
|
tag = 0x06, len = oid.length;
|
|
for (var i = 0; i < len; i++) buf.push(oid[i]);
|
|
}
|
|
else if (val instanceof Array) { // Universal Constructed SEQUENCE
|
|
for (var i = 0; i < val.length; i++) der2b(val[i], buf);
|
|
tag = 0x30, len = buf.length - pos;
|
|
}
|
|
else if (typeof val === 'object' && val.tag === 0x03 && val.value instanceof ArrayBuffer) { // Tag hint
|
|
val = new Uint8Array(val.value), tag = 0x03, len = val.byteLength;
|
|
buf.push(0); for (var i = 0; i < len; i++) buf.push(val[i]);
|
|
len++;
|
|
}
|
|
else {
|
|
throw new Error("Unsupported DER value " + val);
|
|
}
|
|
|
|
if (len >= 0x80) {
|
|
var xlen = len, len = 4;
|
|
buf.splice(pos, 0, (xlen >> 24) & 0xff, (xlen >> 16) & 0xff, (xlen >> 8) & 0xff, xlen & 0xff);
|
|
while (len > 1 && !(xlen >> 24)) xlen <<= 8, len--;
|
|
if (len < 4) buf.splice(pos, 4 - len);
|
|
len |= 0x80;
|
|
}
|
|
|
|
buf.splice(pos - 2, 2, tag, len);
|
|
|
|
return buf;
|
|
}
|
|
|
|
function CryptoKey(key, alg, ext, use) {
|
|
Object.defineProperties(this, {
|
|
_key: {
|
|
value: key
|
|
},
|
|
type: {
|
|
value: key.type,
|
|
enumerable: true,
|
|
},
|
|
extractable: {
|
|
value: (ext === undefined) ? key.extractable : ext,
|
|
enumerable: true,
|
|
},
|
|
algorithm: {
|
|
value: (alg === undefined) ? key.algorithm : alg,
|
|
enumerable: true,
|
|
},
|
|
usages: {
|
|
value: (use === undefined) ? key.usages : use,
|
|
enumerable: true,
|
|
},
|
|
});
|
|
}
|
|
|
|
function isPubKeyUse(u) {
|
|
return u === 'verify' || u === 'encrypt' || u === 'wrapKey';
|
|
}
|
|
|
|
function isPrvKeyUse(u) {
|
|
return u === 'sign' || u === 'decrypt' || u === 'unwrapKey';
|
|
}
|
|
|
|
['generateKey', 'importKey', 'unwrapKey']
|
|
.forEach(function (m) {
|
|
var _fn = _subtle[m];
|
|
|
|
_subtle[m] = function (a, b, c) {
|
|
var args = [].slice.call(arguments),
|
|
ka, kx, ku;
|
|
|
|
switch (m) {
|
|
case 'generateKey':
|
|
ka = alg(a), kx = b, ku = c;
|
|
break;
|
|
case 'importKey':
|
|
ka = alg(c), kx = args[3], ku = args[4];
|
|
if (a === 'jwk') {
|
|
b = b2jwk(b);
|
|
if (!b.alg) b.alg = jwkAlg(ka);
|
|
if (!b.key_ops) b.key_ops = (b.kty !== 'oct') ? ('d' in b) ? ku.filter(isPrvKeyUse) : ku.filter(isPubKeyUse) : ku.slice();
|
|
args[1] = jwk2b(b);
|
|
}
|
|
break;
|
|
case 'unwrapKey':
|
|
ka = args[4], kx = args[5], ku = args[6];
|
|
args[2] = c._key;
|
|
break;
|
|
}
|
|
|
|
if (m === 'generateKey' && ka.name === 'HMAC' && ka.hash) {
|
|
ka.length = ka.length || { 'SHA-1': 512, 'SHA-256': 512, 'SHA-384': 1024, 'SHA-512': 1024 }[ka.hash.name];
|
|
return _subtle.importKey('raw', _crypto.getRandomValues(new Uint8Array((ka.length + 7) >> 3)), ka, kx, ku);
|
|
}
|
|
|
|
if (isWebkit && m === 'generateKey' && ka.name === 'RSASSA-PKCS1-v1_5' && (!ka.modulusLength || ka.modulusLength >= 2048)) {
|
|
a = alg(a), a.name = 'RSAES-PKCS1-v1_5', delete a.hash;
|
|
return _subtle.generateKey(a, true, ['encrypt', 'decrypt'])
|
|
.then(function (k) {
|
|
return Promise.all([
|
|
_subtle.exportKey('jwk', k.publicKey),
|
|
_subtle.exportKey('jwk', k.privateKey),
|
|
]);
|
|
})
|
|
.then(function (keys) {
|
|
keys[0].alg = keys[1].alg = jwkAlg(ka);
|
|
keys[0].key_ops = ku.filter(isPubKeyUse), keys[1].key_ops = ku.filter(isPrvKeyUse);
|
|
return Promise.all([
|
|
_subtle.importKey('jwk', keys[0], ka, true, keys[0].key_ops),
|
|
_subtle.importKey('jwk', keys[1], ka, kx, keys[1].key_ops),
|
|
]);
|
|
})
|
|
.then(function (keys) {
|
|
return {
|
|
publicKey: keys[0],
|
|
privateKey: keys[1],
|
|
};
|
|
});
|
|
}
|
|
|
|
if ((isWebkit || (isIE && (ka.hash || {}).name === 'SHA-1'))
|
|
&& m === 'importKey' && a === 'jwk' && ka.name === 'HMAC' && b.kty === 'oct') {
|
|
return _subtle.importKey('raw', s2b(a2s(b.k)), c, args[3], args[4]);
|
|
}
|
|
|
|
if (isWebkit && m === 'importKey' && (a === 'spki' || a === 'pkcs8')) {
|
|
return _subtle.importKey('jwk', pkcs2jwk(b), c, args[3], args[4]);
|
|
}
|
|
|
|
if (isIE && m === 'unwrapKey') {
|
|
return _subtle.decrypt(args[3], c, b)
|
|
.then(function (k) {
|
|
return _subtle.importKey(a, k, args[4], args[5], args[6]);
|
|
});
|
|
}
|
|
|
|
var op;
|
|
try {
|
|
op = _fn.apply(_subtle, args);
|
|
}
|
|
catch (e) {
|
|
return Promise.reject(e);
|
|
}
|
|
|
|
if (isIE) {
|
|
op = new Promise(function (res, rej) {
|
|
op.onabort =
|
|
op.onerror = function (e) { rej(e) };
|
|
op.oncomplete = function (r) { res(r.target.result) };
|
|
});
|
|
}
|
|
|
|
op = op.then(function (k) {
|
|
if (ka.name === 'HMAC') {
|
|
if (!ka.length) ka.length = 8 * k.algorithm.length;
|
|
}
|
|
if (ka.name.search('RSA') == 0) {
|
|
if (!ka.modulusLength) ka.modulusLength = (k.publicKey || k).algorithm.modulusLength;
|
|
if (!ka.publicExponent) ka.publicExponent = (k.publicKey || k).algorithm.publicExponent;
|
|
}
|
|
if (k.publicKey && k.privateKey) {
|
|
k = {
|
|
publicKey: new CryptoKey(k.publicKey, ka, kx, ku.filter(isPubKeyUse)),
|
|
privateKey: new CryptoKey(k.privateKey, ka, kx, ku.filter(isPrvKeyUse)),
|
|
};
|
|
}
|
|
else {
|
|
k = new CryptoKey(k, ka, kx, ku);
|
|
}
|
|
return k;
|
|
});
|
|
|
|
return op;
|
|
}
|
|
});
|
|
|
|
['exportKey', 'wrapKey']
|
|
.forEach(function (m) {
|
|
var _fn = _subtle[m];
|
|
|
|
_subtle[m] = function (a, b, c) {
|
|
var args = [].slice.call(arguments);
|
|
|
|
switch (m) {
|
|
case 'exportKey':
|
|
args[1] = b._key;
|
|
break;
|
|
case 'wrapKey':
|
|
args[1] = b._key, args[2] = c._key;
|
|
break;
|
|
}
|
|
|
|
if ((isWebkit || (isIE && (b.algorithm.hash || {}).name === 'SHA-1'))
|
|
&& m === 'exportKey' && a === 'jwk' && b.algorithm.name === 'HMAC') {
|
|
args[0] = 'raw';
|
|
}
|
|
|
|
if (isWebkit && m === 'exportKey' && (a === 'spki' || a === 'pkcs8')) {
|
|
args[0] = 'jwk';
|
|
}
|
|
|
|
if (isIE && m === 'wrapKey') {
|
|
return _subtle.exportKey(a, b)
|
|
.then(function (k) {
|
|
if (a === 'jwk') k = s2b(unescape(encodeURIComponent(JSON.stringify(b2jwk(k)))));
|
|
return _subtle.encrypt(args[3], c, k);
|
|
});
|
|
}
|
|
|
|
var op;
|
|
try {
|
|
op = _fn.apply(_subtle, args);
|
|
}
|
|
catch (e) {
|
|
return Promise.reject(e);
|
|
}
|
|
|
|
if (isIE) {
|
|
op = new Promise(function (res, rej) {
|
|
op.onabort =
|
|
op.onerror = function (e) { rej(e) };
|
|
op.oncomplete = function (r) { res(r.target.result) };
|
|
});
|
|
}
|
|
|
|
if (m === 'exportKey' && a === 'jwk') {
|
|
op = op.then(function (k) {
|
|
if ((isWebkit || (isIE && (b.algorithm.hash || {}).name === 'SHA-1'))
|
|
&& b.algorithm.name === 'HMAC') {
|
|
return { 'kty': 'oct', 'alg': jwkAlg(b.algorithm), 'key_ops': b.usages.slice(), 'ext': true, 'k': s2a(b2s(k)) };
|
|
}
|
|
k = b2jwk(k);
|
|
if (!k.alg) k['alg'] = jwkAlg(b.algorithm);
|
|
if (!k.key_ops) k['key_ops'] = (b.type === 'public') ? b.usages.filter(isPubKeyUse) : (b.type === 'private') ? b.usages.filter(isPrvKeyUse) : b.usages.slice();
|
|
return k;
|
|
});
|
|
}
|
|
|
|
if (isWebkit && m === 'exportKey' && (a === 'spki' || a === 'pkcs8')) {
|
|
op = op.then(function (k) {
|
|
k = jwk2pkcs(b2jwk(k));
|
|
return k;
|
|
});
|
|
}
|
|
|
|
return op;
|
|
}
|
|
});
|
|
|
|
['encrypt', 'decrypt', 'sign', 'verify']
|
|
.forEach(function (m) {
|
|
var _fn = _subtle[m];
|
|
|
|
_subtle[m] = function (a, b, c, d) {
|
|
if (isIE && (!c.byteLength || (d && !d.byteLength)))
|
|
throw new Error("Empy input is not allowed");
|
|
|
|
var args = [].slice.call(arguments),
|
|
ka = alg(a);
|
|
|
|
if (isIE && m === 'decrypt' && ka.name === 'AES-GCM') {
|
|
var tl = a.tagLength >> 3;
|
|
args[2] = (c.buffer || c).slice(0, c.byteLength - tl),
|
|
a.tag = (c.buffer || c).slice(c.byteLength - tl);
|
|
}
|
|
|
|
args[1] = b._key;
|
|
|
|
var op;
|
|
try {
|
|
op = _fn.apply(_subtle, args);
|
|
}
|
|
catch (e) {
|
|
return Promise.reject(e);
|
|
}
|
|
|
|
if (isIE) {
|
|
op = new Promise(function (res, rej) {
|
|
op.onabort =
|
|
op.onerror = function (e) {
|
|
rej(e);
|
|
};
|
|
|
|
op.oncomplete = function (r) {
|
|
var r = r.target.result;
|
|
|
|
if (m === 'encrypt' && r instanceof AesGcmEncryptResult) {
|
|
var c = r.ciphertext, t = r.tag;
|
|
r = new Uint8Array(c.byteLength + t.byteLength);
|
|
r.set(new Uint8Array(c), 0);
|
|
r.set(new Uint8Array(t), c.byteLength);
|
|
r = r.buffer;
|
|
}
|
|
|
|
res(r);
|
|
};
|
|
});
|
|
}
|
|
|
|
return op;
|
|
}
|
|
});
|
|
|
|
if (isIE) {
|
|
var _digest = _subtle.digest;
|
|
|
|
_subtle['digest'] = function (a, b) {
|
|
if (!b.byteLength)
|
|
throw new Error("Empy input is not allowed");
|
|
|
|
var op;
|
|
try {
|
|
op = _digest.call(_subtle, a, b);
|
|
}
|
|
catch (e) {
|
|
return Promise.reject(e);
|
|
}
|
|
|
|
op = new Promise(function (res, rej) {
|
|
op.onabort =
|
|
op.onerror = function (e) { rej(e) };
|
|
op.oncomplete = function (r) { res(r.target.result) };
|
|
});
|
|
|
|
return op;
|
|
};
|
|
|
|
global.crypto = Object.create(_crypto, {
|
|
getRandomValues: { value: function (a) { return _crypto.getRandomValues(a) } },
|
|
subtle: { value: _subtle },
|
|
});
|
|
|
|
global.CryptoKey = CryptoKey;
|
|
}
|
|
|
|
if (isWebkit) {
|
|
_crypto.subtle = _subtle;
|
|
|
|
global.Crypto = _Crypto;
|
|
global.SubtleCrypto = _SubtleCrypto;
|
|
global.CryptoKey = CryptoKey;
|
|
}
|
|
}(typeof window === 'undefined' ? typeof self === 'undefined' ? this : self : window);
|