diff --git a/src/app/services/apiService.js b/src/app/services/apiService.js index f7191c89d9e..82d25b04c1c 100644 --- a/src/app/services/apiService.js +++ b/src/app/services/apiService.js @@ -29,6 +29,13 @@ del: { url: _apiUri + '/ciphers/:id/delete', method: 'POST', params: { id: '@id' } } }); + _service.shares = $resource(_apiUri + '/shares/:id', {}, { + get: { method: 'GET', params: { id: '@id' } }, + listCipher: { url: _apiUri + '/shares/:cipherId', method: 'GET', params: { cipherId: '@cipherId' } }, + post: { method: 'POST', params: {} }, + del: { url: _apiUri + '/shares/:id/delete', method: 'POST', params: { id: '@id' } } + }); + _service.accounts = $resource(_apiUri + '/accounts', {}, { register: { url: _apiUri + '/accounts/register', method: 'POST', params: {} }, emailToken: { url: _apiUri + '/accounts/email-token', method: 'POST', params: {} }, diff --git a/src/app/services/cipherService.js b/src/app/services/cipherService.js index c2b8087d996..638827f9248 100644 --- a/src/app/services/cipherService.js +++ b/src/app/services/cipherService.js @@ -18,16 +18,22 @@ angular _service.decryptLogin = function (encryptedLogin) { if (!encryptedLogin) throw "encryptedLogin is undefined or null"; + var key = null; + if (encryptedLogin.Key) { + key = cryptoService.rsaDecrypt(encryptedLogin.Key); + } + var login = { id: encryptedLogin.Id, 'type': 1, folderId: encryptedLogin.FolderId, favorite: encryptedLogin.Favorite, - name: cryptoService.decrypt(encryptedLogin.Name), - uri: encryptedLogin.Uri && encryptedLogin.Uri !== '' ? cryptoService.decrypt(encryptedLogin.Uri) : null, - username: encryptedLogin.Username && encryptedLogin.Username !== '' ? cryptoService.decrypt(encryptedLogin.Username) : null, - password: encryptedLogin.Password && encryptedLogin.Password !== '' ? cryptoService.decrypt(encryptedLogin.Password) : null, - notes: encryptedLogin.Notes && encryptedLogin.Notes !== '' ? cryptoService.decrypt(encryptedLogin.Notes) : null + name: cryptoService.decrypt(encryptedLogin.Name, key), + uri: encryptedLogin.Uri && encryptedLogin.Uri !== '' ? cryptoService.decrypt(encryptedLogin.Uri, key) : null, + username: encryptedLogin.Username && encryptedLogin.Username !== '' ? cryptoService.decrypt(encryptedLogin.Username, key) : null, + password: encryptedLogin.Password && encryptedLogin.Password !== '' ? cryptoService.decrypt(encryptedLogin.Password, key) : null, + notes: encryptedLogin.Notes && encryptedLogin.Notes !== '' ? cryptoService.decrypt(encryptedLogin.Notes, key) : null, + key: encryptedLogin.Key }; if (encryptedLogin.Folder) { @@ -39,6 +45,40 @@ angular return login; }; + _service.decryptLoginPreview = function (encryptedCipher) { + if (!encryptedCipher) throw "encryptedCipher is undefined or null"; + + var key = null; + if (encryptedCipher.Key) { + key = cryptoService.rsaDecrypt(encryptedCipher.Key); + } + + var login = { + id: encryptedCipher.Id, + folderId: encryptedCipher.FolderId, + favorite: encryptedCipher.Favorite, + name: decryptProperty(encryptedCipher.Data.Name, key, false), + username: decryptProperty(encryptedCipher.Data.Username, key, true) + }; + + try { + login.name = cryptoService.decrypt(encryptedCipher.Data.Name, key); + } + catch (err) { + login.name = '[error: cannot decrypt]'; + } + + try { + login.username = encryptedCipher.Data.Username && encryptedCipher.Data.Username !== '' ? + cryptoService.decrypt(encryptedCipher.Data.Username, key) : null; + } + catch (err) { + login.username = '[error: cannot decrypt]'; + } + + return login; + }; + _service.decryptFolders = function (encryptedFolders) { if (!encryptedFolders) throw "encryptedFolders is undefined or null"; @@ -60,6 +100,30 @@ angular }; }; + _service.decryptFolderPreview = function (encryptedFolder) { + if (!encryptedFolder) throw "encryptedFolder is undefined or null"; + + return { + id: encryptedFolder.Id, + name: decryptProperty(encryptedFolder.Data.Name, null, false) + }; + }; + + function decryptProperty(property, key, checkEmpty) { + if (checkEmpty && (!property || property === '')) { + return null; + } + + try { + property = cryptoService.decrypt(property, key); + } + catch (err) { + property = '[error: cannot decrypt]'; + } + + return property; + } + _service.encryptLogins = function (unencryptedLogins, key) { if (!unencryptedLogins) throw "unencryptedLogins is undefined or null"; diff --git a/src/app/services/cryptoService.js b/src/app/services/cryptoService.js index 983ede83e62..9848a334632 100644 --- a/src/app/services/cryptoService.js +++ b/src/app/services/cryptoService.js @@ -5,7 +5,8 @@ angular var _service = {}, _key, _b64Key, - _privateKey; + _privateKey, + _publicKey; _service.setKey = function (key) { _key = key; @@ -64,25 +65,41 @@ angular } if ($sessionStorage.privateKey) { - _privateKey = forge.util.decode64($sessionStorage.privateKey); + var privateKeyBytes = forge.util.decode64($sessionStorage.privateKey); + _privateKey = forge.pki.privateKeyFromAsn1(forge.asn1.fromDer(privateKeyBytes)); } return _privateKey; }; + _service.getPublicKey = function () { + if (_publicKey) { + return _publicKey; + } + + var privateKey = _service.getPrivateKey(); + if (!privateKey) { + return null; + } + + _publicKey = forge.pki.setRsaPublicKey(privateKey.n, privateKey.e); + return _publicKey; + }; + _service.clearKey = function () { _key = _b64Key = null; delete $sessionStorage.key; }; - _service.clearPrivateKey = function () { + _service.clearKeyPair = function () { _privateKey = null; + _publicKey = null; delete $sessionStorage.privateKey; }; _service.clearKeys = function () { _service.clearKey(); - _service.clearPrivateKey(); + _service.clearKeyPair(); }; _service.makeKey = function (password, salt, b64) { @@ -114,6 +131,10 @@ angular }); }; + _service.makeShareKey = function () { + return forge.random.getBytesSync(32); + }; + _service.hashPassword = function (password, key) { if (!key) { key = _service.getKey(); @@ -163,6 +184,18 @@ angular return cipherString; }; + _service.rsaEncrypt = function (plainValue, publicKey) { + publicKey = publicKey || _service.getPublicKey(); + if (!publicKey) { + throw 'Public key unavailable.'; + } + + var encryptedBytes = publicKey.encrypt(plainValue, 'RSA-OAEP', { + md: forge.md.sha256.create() + }); + return forge.util.encode64(encryptedBytes); + } + _service.decrypt = function (encValue, key, outputEncoding) { if (!_service.getKey() && !key) { throw 'Encryption key unavailable.'; @@ -208,6 +241,20 @@ angular } }; + _service.rsaDecrypt = function (encValue, privateKey) { + privateKey = privateKey || _service.getPrivateKey(); + if (!privateKey) { + throw 'Private key unavailable.'; + } + + var ctBytes = forge.util.decode64(encValue); + var decBytes = privateKey.decrypt(ctBytes, 'RSA-OAEP', { + md: forge.md.sha256.create() + }); + + return decBytes; + } + function computeMac(ct, iv, macKey) { var hmac = forge.hmac.create(); hmac.start('sha256', macKey || _service.getMacKey()); diff --git a/src/app/vault/vaultController.js b/src/app/vault/vaultController.js index e9b3e6c4e6c..4d76a062bae 100644 --- a/src/app/vault/vaultController.js +++ b/src/app/vault/vaultController.js @@ -1,7 +1,8 @@ angular .module('bit.vault') - .controller('vaultController', function ($scope, $uibModal, apiService, $filter, cryptoService, authService, toastr, cipherService) { + .controller('vaultController', function ($scope, $uibModal, apiService, $filter, cryptoService, authService, toastr, + cipherService) { $scope.logins = []; $scope.folders = []; @@ -17,30 +18,11 @@ for (var i = 0; i < ciphers.Data.length; i++) { if (ciphers.Data[i].Type === 0) { - var decFolder = { - id: ciphers.Data[i].Id - }; - - try { decFolder.name = cryptoService.decrypt(ciphers.Data[i].Data.Name); } - catch (err) { decFolder.name = '[error: cannot decrypt]'; } - + var decFolder = cipherService.decryptFolderPreview(ciphers.Data[i]); decFolders.push(decFolder); } else { - var decLogin = { - id: ciphers.Data[i].Id, - folderId: ciphers.Data[i].FolderId, - favorite: ciphers.Data[i].Favorite - }; - - try { decLogin.name = cryptoService.decrypt(ciphers.Data[i].Data.Name); } - catch (err) { decLogin.name = '[error: cannot decrypt]'; } - - if (ciphers.Data[i].Data.Username) { - try { decLogin.username = cryptoService.decrypt(ciphers.Data[i].Data.Username); } - catch (err) { decLogin.username = '[error: cannot decrypt]'; } - } - + var decLogin = cipherService.decryptLoginPreview(ciphers.Data[i]); decLogins.push(decLogin); } } @@ -183,28 +165,18 @@ }; $scope.shareLogin = function (login) { - share(login.id, login.name, false); - }; - - $scope.shareFolder = function (folder) { - share(folder.id, folder.name, true); - }; - - function share(id, name, isFolder) { var shareModel = $uibModal.open({ animation: true, - templateUrl: 'app/vault/views/vaultShare.html', - controller: 'vaultShareController', + templateUrl: 'app/vault/views/vaultShareLogin.html', + controller: 'vaultShareLoginController', size: 'lg', resolve: { - id: function () { return id; }, - name: function () { return name; }, - isFolder: function () { return isFolder; } + id: function () { return login.id; } } }); shareModel.result.then(function (result) { }); - } + }; }); diff --git a/src/app/vault/vaultShareController.js b/src/app/vault/vaultShareController.js deleted file mode 100644 index 4d344c49f9b..00000000000 --- a/src/app/vault/vaultShareController.js +++ /dev/null @@ -1,21 +0,0 @@ -angular - .module('bit.vault') - - .controller('vaultShareController', function ($scope, apiService, $uibModalInstance, cryptoService, cipherService, - id, name, isFolder, $analytics) { - $analytics.eventTrack('vaultShareController', { category: 'Modal' }); - $scope.cipher = { - id: id, - name: name, - isFolder: isFolder - }; - - $scope.sharePromise = null; - $scope.share = function () { - $uibModalInstance.close({}); - }; - - $scope.close = function () { - $uibModalInstance.dismiss('cancel'); - }; - }); diff --git a/src/app/vault/vaultShareLoginController.js b/src/app/vault/vaultShareLoginController.js new file mode 100644 index 00000000000..d72d4ba97a7 --- /dev/null +++ b/src/app/vault/vaultShareLoginController.js @@ -0,0 +1,33 @@ +angular + .module('bit.vault') + + .controller('vaultShareLoginController', function ($scope, apiService, $uibModalInstance, cryptoService, cipherService, + id, $analytics) { + $analytics.eventTrack('vaultShareLoginController', { category: 'Modal' }); + + apiService.logins.get({ + id: id + }, function (login) { + $scope.login = cipherService.decryptLogin(login); + }); + + $scope.enablePromise = null; + $scope.enable = function () { + var shareKey = cryptoService.makeShareKey(); + var encLogin = cipherService.encryptLogin($scope.login, shareKey); + encLogin.key = cryptoService.rsaEncrypt(shareKey); + + $scope.enablePromise = apiService.logins.put({ id: $scope.login.id }, encLogin, function (login) { + $scope.login = cipherService.decryptLogin(login); + }).$promise; + }; + + $scope.sharePromise = null; + $scope.share = function () { + $uibModalInstance.close({}); + }; + + $scope.close = function () { + $uibModalInstance.dismiss('cancel'); + }; + }); diff --git a/src/app/vault/views/vaultShare.html b/src/app/vault/views/vaultShareLogin.html similarity index 59% rename from src/app/vault/views/vaultShare.html rename to src/app/vault/views/vaultShareLogin.html index 096bfe6a686..4d2aebd73f8 100644 --- a/src/app/vault/views/vaultShare.html +++ b/src/app/vault/views/vaultShareLogin.html @@ -1,13 +1,27 @@