mirror of
https://github.com/bitwarden/browser.git
synced 2024-11-28 12:45:45 +01:00
download and decrypt attachments
This commit is contained in:
parent
7b4cf53ec4
commit
7ff79a0fdd
@ -80,6 +80,7 @@ angular
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
id: encryptedAttachment.Id,
|
id: encryptedAttachment.Id,
|
||||||
|
url: encryptedAttachment.Url,
|
||||||
fileName: cryptoService.decrypt(encryptedAttachment.FileName, key),
|
fileName: cryptoService.decrypt(encryptedAttachment.FileName, key),
|
||||||
size: encryptedAttachment.SizeName
|
size: encryptedAttachment.SizeName
|
||||||
};
|
};
|
||||||
|
@ -385,7 +385,11 @@ angular
|
|||||||
if (!keyBuf.macKey) {
|
if (!keyBuf.macKey) {
|
||||||
return null;
|
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) {
|
}).then(function (mac) {
|
||||||
if (mac) {
|
if (mac) {
|
||||||
obj.mac = new Uint8Array(mac);
|
obj.mac = new Uint8Array(mac);
|
||||||
@ -458,10 +462,6 @@ angular
|
|||||||
|
|
||||||
switch (encType) {
|
switch (encType) {
|
||||||
case constants.encType.AesCbc128_HmacSha256_B64:
|
case constants.encType.AesCbc128_HmacSha256_B64:
|
||||||
if (encPieces.length !== 3) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case constants.encType.AesCbc256_HmacSha256_B64:
|
case constants.encType.AesCbc256_HmacSha256_B64:
|
||||||
if (encPieces.length !== 3) {
|
if (encPieces.length !== 3) {
|
||||||
return null;
|
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) {
|
_service.rsaDecrypt = function (encValue, privateKey, key) {
|
||||||
privateKey = privateKey || _service.getPrivateKey();
|
privateKey = privateKey || _service.getPrivateKey();
|
||||||
key = key || _service.getEncKey();
|
key = key || _service.getEncKey();
|
||||||
@ -585,10 +663,10 @@ angular
|
|||||||
return b64Output ? forge.util.encode64(mac.getBytes()) : mac.getBytes();
|
return b64Output ? forge.util.encode64(mac.getBytes()) : mac.getBytes();
|
||||||
}
|
}
|
||||||
|
|
||||||
function computeMacWC(data, macKey) {
|
function computeMacWC(dataBuf, macKeyBuf) {
|
||||||
return window.crypto.subtle.importKey('raw', macKey, { name: 'HMAC', hash: { name: 'SHA-256' } }, false, ['sign'])
|
return window.crypto.subtle.importKey('raw', macKeyBuf, { name: 'HMAC', hash: { name: 'SHA-256' } }, false, ['sign'])
|
||||||
.then(function (key) {
|
.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;
|
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) {
|
function SymmetricCryptoKey(keyBytes, b64KeyBytes, encType) {
|
||||||
if (b64KeyBytes) {
|
if (b64KeyBytes) {
|
||||||
keyBytes = forge.util.decode64(keyBytes);
|
keyBytes = forge.util.decode64(keyBytes);
|
||||||
@ -657,6 +764,10 @@ angular
|
|||||||
}
|
}
|
||||||
|
|
||||||
SymmetricCryptoKey.prototype.getBuffers = function () {
|
SymmetricCryptoKey.prototype.getBuffers = function () {
|
||||||
|
if (this.keyBuf) {
|
||||||
|
return this.keyBuf;
|
||||||
|
}
|
||||||
|
|
||||||
var key = b64ToArray(this.keyB64);
|
var key = b64ToArray(this.keyB64);
|
||||||
|
|
||||||
var keys = {
|
var keys = {
|
||||||
@ -672,7 +783,8 @@ angular
|
|||||||
keys.macKey = null;
|
keys.macKey = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return keys;
|
this.keyBuf = keys;
|
||||||
|
return this.keyBuf;
|
||||||
};
|
};
|
||||||
|
|
||||||
function b64ToArray(b64Str) {
|
function b64ToArray(b64Str) {
|
||||||
|
@ -24,12 +24,21 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
var file = files[0];
|
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();
|
var reader = new FileReader();
|
||||||
reader.readAsArrayBuffer(file);
|
reader.readAsArrayBuffer(file);
|
||||||
reader.onload = function (evt) {
|
reader.onload = function (evt) {
|
||||||
var key = null;
|
form.$loading = true;
|
||||||
|
$scope.$apply();
|
||||||
|
|
||||||
|
var key = getKeyForLogin();
|
||||||
|
|
||||||
var encFilename = cryptoService.encrypt(file.name, key);
|
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 fd = new FormData();
|
||||||
var blob = new Blob([encData], { type: 'application/octet-stream' });
|
var blob = new Blob([encData], { type: 'application/octet-stream' });
|
||||||
fd.append('data', blob, encFilename);
|
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 () {
|
$scope.close = function () {
|
||||||
$uibModalInstance.dismiss('cancel');
|
$uibModalInstance.dismiss('cancel');
|
||||||
};
|
};
|
||||||
|
@ -36,6 +36,7 @@
|
|||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="#" stop-click ng-click="download(attachment)">{{attachment.fileName}}</a>
|
<a href="#" stop-click ng-click="download(attachment)">{{attachment.fileName}}</a>
|
||||||
|
<i class="fa fa-spinner fa-spin text-muted" ng-if="attachment.loading"></i>
|
||||||
</td>
|
</td>
|
||||||
<td style="width: 80px; min-width: 80px;">
|
<td style="width: 80px; min-width: 80px;">
|
||||||
{{attachment.size}}
|
{{attachment.size}}
|
||||||
@ -55,6 +56,7 @@
|
|||||||
<div class="form-group" show-error>
|
<div class="form-group" show-error>
|
||||||
<label for="file" class="sr-only">File</label>
|
<label for="file" class="sr-only">File</label>
|
||||||
<input type="file" id="file" name="file" />
|
<input type="file" id="file" name="file" />
|
||||||
|
<p class="help-block">Maximum size per file is 100 MB.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
|
Loading…
Reference in New Issue
Block a user