From b62950fa2bf5400c4519bc50e0c41bb41ad2dccc Mon Sep 17 00:00:00 2001 From: Kyle Spearrin Date: Sat, 8 Jul 2017 00:12:57 -0400 Subject: [PATCH] IE fixes and crypto shims --- gulpfile.js | 4 + package.json | 3 +- src/app/app.js | 1 + src/app/config.js | 6 + src/app/directives/totpDirective.js | 2 +- .../organizationVaultAttachmentsController.js | 17 +- src/app/services/cryptoService.js | 64 +- .../settings/settingsTwoStepU2fController.js | 10 +- src/app/vault/vaultAttachmentsController.js | 19 +- src/app/vault/views/vaultAttachments.html | 2 +- src/index.html | 2 + src/js/u2f-api.js | 2 +- src/js/webcrypto-shim.js | 599 ++++++++++++++++++ src/less/vault.less | 2 +- 14 files changed, 698 insertions(+), 35 deletions(-) create mode 100644 src/js/webcrypto-shim.js diff --git a/gulpfile.js b/gulpfile.js index efc3cdb13e..9a584b280d 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -182,6 +182,10 @@ gulp.task('lib', ['clean:lib'], function () { { src: paths.npmDir + 'duo_web_sdk/index.js', dest: paths.libDir + 'duo' + }, + { + src: paths.npmDir + 'angular-promise-polyfill/index.js', + dest: paths.libDir + 'angular-promise-polyfill' } ]; diff --git a/package.json b/package.json index 4259a1f2dc..8fd7fcc510 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,7 @@ "vinyl-source-stream": "1.1.0", "gulp-derequire": "2.1.0", "exposify": "0.5.0", - "duo_web_sdk": "git+https://github.com/duosecurity/duo_web_sdk.git" + "duo_web_sdk": "git+https://github.com/duosecurity/duo_web_sdk.git", + "angular-promise-polyfill": "0.0.4" } } diff --git a/src/app/app.js b/src/app/app.js index f17e0172a4..d1d8f18a32 100644 --- a/src/app/app.js +++ b/src/app/app.js @@ -9,6 +9,7 @@ 'angulartics.google.analytics', 'angular-stripe', 'credit-cards', + 'angular-promise-polyfill', 'bit.directives', 'bit.filters', diff --git a/src/app/config.js b/src/app/config.js index 342fad87c9..9e37effb96 100644 --- a/src/app/config.js +++ b/src/app/config.js @@ -55,6 +55,12 @@ angular $httpProvider.defaults.headers.post['Content-Type'] = 'text/plain; charset=utf-8'; + if (!$httpProvider.defaults.headers.get) { + $httpProvider.defaults.headers.get = {}; + } + $httpProvider.defaults.headers.get['Cache-Control'] = 'no-cache'; + $httpProvider.defaults.headers.get['Pragma'] = 'no-cache'; + $httpProvider.interceptors.push('apiInterceptor'); $httpProvider.interceptors.push('jwtInterceptor'); diff --git a/src/app/directives/totpDirective.js b/src/app/directives/totpDirective.js index 365b87f9d5..163273586c 100644 --- a/src/app/directives/totpDirective.js +++ b/src/app/directives/totpDirective.js @@ -76,7 +76,7 @@ angular var sign = function (keyBytes, timeBytes) { return window.crypto.subtle.importKey('raw', keyBytes, { name: 'HMAC', hash: { name: 'SHA-1' } }, false, ['sign']).then(function (key) { - return window.crypto.subtle.sign({ name: 'HMAC' }, key, timeBytes); + return window.crypto.subtle.sign({ name: 'HMAC', hash: { name: 'SHA-1' } }, key, timeBytes); }).then(function (signature) { return buff2hex(signature); }).catch(function (err) { diff --git a/src/app/organization/organizationVaultAttachmentsController.js b/src/app/organization/organizationVaultAttachmentsController.js index 33ff74739e..99b1714e15 100644 --- a/src/app/organization/organizationVaultAttachmentsController.js +++ b/src/app/organization/organizationVaultAttachmentsController.js @@ -2,7 +2,7 @@ .module('bit.organization') .controller('organizationVaultAttachmentsController', function ($scope, apiService, $uibModalInstance, cryptoService, - cipherService, loginId, $analytics, validationService, toastr) { + cipherService, loginId, $analytics, validationService, toastr, $timeout) { $analytics.eventTrack('organizationVaultAttachmentsController', { category: 'Modal' }); $scope.login = {}; $scope.loading = true; @@ -31,8 +31,9 @@ var reader = new FileReader(); reader.readAsArrayBuffer(file); reader.onload = function (evt) { - form.$loading = true; - $scope.$apply(); + $timeout(function () { + form.$loading = true; + }); var key = cryptoService.getOrgKey($scope.login.organizationId); var encFilename = cryptoService.encrypt(file.name, key); @@ -61,8 +62,9 @@ req.responseType = 'arraybuffer'; req.onload = function (evt) { if (!req.response) { - attachment.loading = false; - $scope.$apply(); + $timeout(function () { + attachment.loading = false; + }); // error return; @@ -85,8 +87,9 @@ document.body.removeChild(a); } - attachment.loading = false; - $scope.$apply(); + $timeout(function () { + attachment.loading = false; + }); }); }; req.send(null); diff --git a/src/app/services/cryptoService.js b/src/app/services/cryptoService.js index 3937ef8c61..e8877425c5 100644 --- a/src/app/services/cryptoService.js +++ b/src/app/services/cryptoService.js @@ -521,17 +521,17 @@ angular return null; } - ivBytes = encBytes.slice(1, 17); - macBytes = encBytes.slice(17, 49); - ctBytes = encBytes.slice(49); + ivBytes = slice(encBytes, 1, 17); + macBytes = slice(encBytes, 17, 49); + ctBytes = slice(encBytes, 49); break; case constants.encType.AesCbc256_B64: if (encBytes.length <= 17) { // 1 + 16 + ctLength return null; } - ivBytes = encBytes.slice(1, 17); - ctBytes = encBytes.slice(17); + ivBytes = slice(encBytes, 1, 17); + ctBytes = slice(encBytes, 17); break; default: return null; @@ -578,7 +578,6 @@ angular } return window.crypto.subtle.decrypt({ name: 'AES-CBC', iv: ivBuf }, encKey, ctBuf); }); - } _service.rsaDecrypt = function (encValue, privateKey, key) { @@ -666,7 +665,7 @@ angular function computeMacWC(dataBuf, macKeyBuf) { return window.crypto.subtle.importKey('raw', macKeyBuf, { name: 'HMAC', hash: { name: 'SHA-256' } }, false, ['sign']) .then(function (key) { - return window.crypto.subtle.sign({ name: 'HMAC' }, key, dataBuf); + return window.crypto.subtle.sign({ name: 'HMAC', hash: { name: 'SHA-256' } }, key, dataBuf); }); } @@ -693,10 +692,10 @@ angular return window.crypto.subtle.importKey('raw', macKeyBuf, { name: 'HMAC', hash: { name: 'SHA-256' } }, false, ['sign']) .then(function (key) { macKey = key; - return window.crypto.subtle.sign({ name: 'HMAC' }, macKey, mac1Buf); + return window.crypto.subtle.sign({ name: 'HMAC', hash: { name: 'SHA-256' } }, macKey, mac1Buf); }).then(function (mac) { mac1 = mac; - return window.crypto.subtle.sign({ name: 'HMAC' }, macKey, mac2Buf); + return window.crypto.subtle.sign({ name: 'HMAC', hash: { name: 'SHA-256' } }, macKey, mac2Buf); }).then(function (mac2) { if (mac1.byteLength !== mac2.byteLength) { return false; @@ -775,8 +774,8 @@ angular }; if (this.macKey) { - keys.encKey = key.slice(0, key.length / 2).buffer; - keys.macKey = key.slice(key.length / 2).buffer; + keys.encKey = slice(key, 0, key.length / 2).buffer; + keys.macKey = slice(key, key.length / 2).buffer; } else { keys.encKey = key.buffer; @@ -796,5 +795,48 @@ angular return arr; } + function slice(arr, begin, end) { + if (arr.slice) { + return arr.slice(begin, end); + } + + // shim for IE + // ref: https://stackoverflow.com/a/21440217 + + arr = arr.buffer; + if (begin === void 0) { + begin = 0; + } + + if (end === void 0) { + end = arr.byteLength; + } + + begin = Math.floor(begin); + end = Math.floor(end); + + if (begin < 0) { + begin += arr.byteLength; + } + + if (end < 0) { + end += arr.byteLength; + } + + begin = Math.min(Math.max(0, begin), arr.byteLength); + end = Math.min(Math.max(0, end), arr.byteLength); + + if (end - begin <= 0) { + return new ArrayBuffer(0); + } + + var result = new ArrayBuffer(end - begin); + var resultBytes = new Uint8Array(result); + var sourceBytes = new Uint8Array(arr, begin, end - begin); + + resultBytes.set(sourceBytes); + return new Uint8Array(result); + } + return _service; }); \ No newline at end of file diff --git a/src/app/settings/settingsTwoStepU2fController.js b/src/app/settings/settingsTwoStepU2fController.js index 26062a1f09..1ed689834c 100644 --- a/src/app/settings/settingsTwoStepU2fController.js +++ b/src/app/settings/settingsTwoStepU2fController.js @@ -45,14 +45,16 @@ return; } else if (data.errorCode) { - $scope.deviceError = true; - $scope.$apply(); + $timeout(function () { + $scope.deviceError = true; + }); console.log('error: ' + data.errorCode); return; } - $scope.deviceResponse = JSON.stringify(data); - $scope.$apply(); + $timeout(function () { + $scope.deviceResponse = JSON.stringify(data); + }); }, 10); }; diff --git a/src/app/vault/vaultAttachmentsController.js b/src/app/vault/vaultAttachmentsController.js index 4b9446cae5..aff402d788 100644 --- a/src/app/vault/vaultAttachmentsController.js +++ b/src/app/vault/vaultAttachmentsController.js @@ -2,7 +2,7 @@ .module('bit.vault') .controller('vaultAttachmentsController', function ($scope, apiService, $uibModalInstance, cryptoService, cipherService, - loginId, $analytics, validationService, toastr) { + loginId, $analytics, validationService, toastr, $timeout) { $analytics.eventTrack('vaultAttachmentsController', { category: 'Modal' }); $scope.login = {}; $scope.readOnly = true; @@ -33,8 +33,9 @@ var reader = new FileReader(); reader.readAsArrayBuffer(file); reader.onload = function (evt) { - form.$loading = true; - $scope.$apply(); + $timeout(function () { + form.$loading = true; + }); var key = getKeyForLogin(); @@ -65,8 +66,9 @@ req.responseType = 'arraybuffer'; req.onload = function (evt) { if (!req.response) { - attachment.loading = false; - $scope.$apply(); + $timeout(function () { + attachment.loading = false; + }); // error return; @@ -87,9 +89,10 @@ a.click(); document.body.removeChild(a); } - - attachment.loading = false; - $scope.$apply(); + + $timeout(function () { + attachment.loading = false; + }); }); }; req.send(null); diff --git a/src/app/vault/views/vaultAttachments.html b/src/app/vault/views/vaultAttachments.html index b603eef314..46d5ac70b6 100644 --- a/src/app/vault/views/vaultAttachments.html +++ b/src/app/vault/views/vaultAttachments.html @@ -38,7 +38,7 @@ {{attachment.fileName}} - + {{attachment.size}} diff --git a/src/index.html b/src/index.html index ccc40953f8..6a63d13269 100644 --- a/src/index.html +++ b/src/index.html @@ -89,8 +89,10 @@ + + diff --git a/src/js/u2f-api.js b/src/js/u2f-api.js index bba8a3a1e8..fe196dd1bc 100644 --- a/src/js/u2f-api.js +++ b/src/js/u2f-api.js @@ -22,7 +22,7 @@ var u2f = u2f || {}; * Modification: * Check if browser supports U2F API before this wrapper was added. */ -u2f.isSupported = !!(((typeof u2f !== 'undefined') && u2f.register) || (chrome && chrome.runtime)); +u2f.isSupported = !!(((typeof u2f !== 'undefined') && u2f.register) || ((typeof chrome !== 'undefined') && chrome.runtime)); /** * FIDO U2F Javascript API Version diff --git a/src/js/webcrypto-shim.js b/src/js/webcrypto-shim.js new file mode 100644 index 0000000000..1b8e89aca2 --- /dev/null +++ b/src/js/webcrypto-shim.js @@ -0,0 +1,599 @@ +/** + * @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; + + 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); diff --git a/src/less/vault.less b/src/less/vault.less index b10e2bdb89..cd5d43db74 100644 --- a/src/less/vault.less +++ b/src/less/vault.less @@ -675,7 +675,7 @@ h1, h2, h3, h4, h5, h6 { &.inner { stroke-width: 3; stroke-dasharray: 78.6; - stroke-dashoffset: 20px; + stroke-dashoffset: 0px; } &.outer {