diff --git a/src/app/services/cipherService.js b/src/app/services/cipherService.js index af1c3d2c23..9d35047564 100644 --- a/src/app/services/cipherService.js +++ b/src/app/services/cipherService.js @@ -80,6 +80,7 @@ angular return { id: encryptedAttachment.Id, + url: encryptedAttachment.Url, fileName: cryptoService.decrypt(encryptedAttachment.FileName, key), size: encryptedAttachment.SizeName }; diff --git a/src/app/services/cryptoService.js b/src/app/services/cryptoService.js index 4f3017ec44..3937ef8c61 100644 --- a/src/app/services/cryptoService.js +++ b/src/app/services/cryptoService.js @@ -385,7 +385,11 @@ angular if (!keyBuf.macKey) { return null; } - return computeMacWC(encValue, keyBuf.macKey); + + var data = new Uint8Array(obj.iv.length + obj.ct.length); + data.set(obj.iv, 0); + data.set(obj.ct, obj.iv.length); + return computeMacWC(data.buffer, keyBuf.macKey); }).then(function (mac) { if (mac) { obj.mac = new Uint8Array(mac); @@ -458,10 +462,6 @@ angular switch (encType) { case constants.encType.AesCbc128_HmacSha256_B64: - if (encPieces.length !== 3) { - return null; - } - break; case constants.encType.AesCbc256_HmacSha256_B64: if (encPieces.length !== 3) { return null; @@ -503,6 +503,84 @@ angular } }; + _service.decryptFromBytes = function (encBuf, key) { + if (!encBuf) { + throw 'no encBuf.'; + } + + var encBytes = new Uint8Array(encBuf), + encType = encBytes[0], + ctBytes = null, + ivBytes = null, + macBytes = null; + + switch (encType) { + case constants.encType.AesCbc128_HmacSha256_B64: + case constants.encType.AesCbc256_HmacSha256_B64: + if (encBytes.length <= 49) { // 1 + 16 + 32 + ctLength + return null; + } + + ivBytes = encBytes.slice(1, 17); + macBytes = encBytes.slice(17, 49); + ctBytes = encBytes.slice(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); + break; + default: + return null; + } + + return aesDecryptWC( + encType, + ctBytes.buffer, + ivBytes.buffer, + macBytes ? macBytes.buffer : null, + key); + }; + + function aesDecryptWC(encType, ctBuf, ivBuf, macBuf, key) { + key = key || _service.getEncKey() || _service.getKey(); + if (!key) { + throw 'Encryption key unavailable.'; + } + + var keyBuf = key.getBuffers(), + encKey = null; + + return window.crypto.subtle.importKey('raw', keyBuf.encKey, { name: 'AES-CBC' }, false, ['decrypt']) + .then(function (theEncKey) { + encKey = theEncKey; + + if (!key.macKey || !macBuf) { + return null; + } + + var data = new Uint8Array(ivBuf.byteLength + ctBuf.byteLength); + data.set(new Uint8Array(ivBuf), 0); + data.set(new Uint8Array(ctBuf), ivBuf.byteLength); + return computeMacWC(data.buffer, keyBuf.macKey); + }).then(function (computedMacBuf) { + if (computedMacBuf === null) { + return null; + } + return macsEqualWC(keyBuf.macKey, macBuf, computedMacBuf); + }).then(function (macsMatch) { + if (macsMatch === false) { + console.error('MAC failed.'); + return null; + } + return window.crypto.subtle.decrypt({ name: 'AES-CBC', iv: ivBuf }, encKey, ctBuf); + }); + + } + _service.rsaDecrypt = function (encValue, privateKey, key) { privateKey = privateKey || _service.getPrivateKey(); key = key || _service.getEncKey(); @@ -585,10 +663,10 @@ angular return b64Output ? forge.util.encode64(mac.getBytes()) : mac.getBytes(); } - function computeMacWC(data, macKey) { - return window.crypto.subtle.importKey('raw', macKey, { name: 'HMAC', hash: { name: 'SHA-256' } }, false, ['sign']) + 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, data); + return window.crypto.subtle.sign({ name: 'HMAC' }, key, dataBuf); }); } @@ -608,6 +686,35 @@ angular return mac1 === mac2; } + function macsEqualWC(macKeyBuf, mac1Buf, mac2Buf) { + var mac1, + macKey; + + 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); + }).then(function (mac) { + mac1 = mac; + return window.crypto.subtle.sign({ name: 'HMAC' }, macKey, mac2Buf); + }).then(function (mac2) { + if (mac1.byteLength !== mac2.byteLength) { + return false; + } + + var arr1 = new Uint8Array(mac1); + var arr2 = new Uint8Array(mac2); + + for (var i = 0; i < arr2.length; i++) { + if (arr1[i] !== arr2[i]) { + return false; + } + } + + return true; + }); + } + function SymmetricCryptoKey(keyBytes, b64KeyBytes, encType) { if (b64KeyBytes) { keyBytes = forge.util.decode64(keyBytes); @@ -657,6 +764,10 @@ angular } SymmetricCryptoKey.prototype.getBuffers = function () { + if (this.keyBuf) { + return this.keyBuf; + } + var key = b64ToArray(this.keyB64); var keys = { @@ -672,7 +783,8 @@ angular keys.macKey = null; } - return keys; + this.keyBuf = keys; + return this.keyBuf; }; function b64ToArray(b64Str) { diff --git a/src/app/vault/vaultAttachmentsController.js b/src/app/vault/vaultAttachmentsController.js index 53a184632f..32cb164402 100644 --- a/src/app/vault/vaultAttachmentsController.js +++ b/src/app/vault/vaultAttachmentsController.js @@ -24,12 +24,21 @@ } var file = files[0]; + if (file.size > 104857600) { // 100 MB + validationService.addError(form, 'file', 'Maximum file size is 100 MB.', true); + return; + } + var reader = new FileReader(); reader.readAsArrayBuffer(file); reader.onload = function (evt) { - var key = null; + form.$loading = true; + $scope.$apply(); + + var key = getKeyForLogin(); + var encFilename = cryptoService.encrypt(file.name, key); - cryptoService.encryptToBytes(evt.target.result, key).then(function (encData) { + $scope.savePromise = cryptoService.encryptToBytes(evt.target.result, key).then(function (encData) { var fd = new FormData(); var blob = new Blob([encData], { type: 'application/octet-stream' }); fd.append('data', blob, encFilename); @@ -47,6 +56,53 @@ }; } + $scope.download = function (attachment) { + attachment.loading = true; + var key = getKeyForLogin(); + + var req = new XMLHttpRequest(); + req.open('GET', attachment.url, true); + req.responseType = 'arraybuffer'; + req.onload = function (evt) { + if (!req.response) { + attachment.loading = false; + $scope.$apply(); + + // error + return; + } + + cryptoService.decryptFromBytes(req.response, key).then(function (decBuf) { + var blob = new Blob([decBuf]); + + // IE hack. ref http://msdn.microsoft.com/en-us/library/ie/hh779016.aspx + if (window.navigator.msSaveOrOpenBlob) { + window.navigator.msSaveBlob(blob, attachment.fileName); + } + else { + var a = window.document.createElement('a'); + a.href = window.URL.createObjectURL(blob); + a.download = attachment.fileName; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + } + + attachment.loading = false; + $scope.$apply(); + }); + }; + req.send(null); + }; + + function getKeyForLogin() { + if ($scope.login.organizationId) { + return cryptoService.getOrgKey($scope.login.organizationId); + } + + return null; + } + $scope.close = function () { $uibModalInstance.dismiss('cancel'); }; diff --git a/src/app/vault/views/vaultAttachments.html b/src/app/vault/views/vaultAttachments.html index 7b0d8f700c..7a2a638dbe 100644 --- a/src/app/vault/views/vaultAttachments.html +++ b/src/app/vault/views/vaultAttachments.html @@ -36,6 +36,7 @@ {{attachment.fileName}} + {{attachment.size}} @@ -55,6 +56,7 @@
+

Maximum size per file is 100 MB.