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 {