(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.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} 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} signRequests * @param {Array} 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} * @private */ u2f.waitingForPort_ = []; /** * A counter for requestIds. * @type {number} * @private */ u2f.reqCounter_ = 0; /** * A map from requestIds to client callbacks * @type {Object.} * @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.} 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} 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} 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} registerRequests * @param {Array} 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} registerRequests * @param {Array} 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 * @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);