diff --git a/src/background.js b/src/background.js index 540c9cfbfe..c354fe77a8 100644 --- a/src/background.js +++ b/src/background.js @@ -86,7 +86,7 @@ chrome.runtime.onMessage.addListener(function (msg, sender, sendResponse) { setIcon(); function setIcon() { userService.isAuthenticated(function (isAuthenticated) { - cryptoService.getKey(false, function (key) { + cryptoService.getKey(function (key) { var suffix = ''; if (!isAuthenticated) { suffix = '_gray'; @@ -737,7 +737,7 @@ function checkLock() { return; } - cryptoService.getKey(false, function (key) { + cryptoService.getKey(function (key) { if (!key) { // no key so no need to lock return; diff --git a/src/models/domainModels.js b/src/models/domainModels.js index a7bf55b84f..b8f339f01b 100644 --- a/src/models/domainModels.js +++ b/src/models/domainModels.js @@ -1,13 +1,87 @@ -var CipherString = function (encryptedString) { - this.encryptedString = encryptedString; +var CipherString = function () { + this.encryptedString = null; + this.encryptionType = null; this.decryptedValue = null; + this.cipherText = null; + this.initializationVector = null; + this.mac = null; - if (encryptedString) { - var encPieces = this.encryptedString.split('|'); + if (arguments.length >= 2) { + this.encryptedString = arguments[0] + '.' + arguments[1]; - this.initializationVector = encPieces[0]; - this.cipherText = encPieces[1]; - this.mac = encPieces.length > 2 ? encPieces[2] : null; + if (arguments.length > 2 && arguments[2]) { + this.encryptedString += ('|' + arguments[2]); + } + + if (arguments.length > 3 && arguments[3]) { + this.encryptedString += ('|' + arguments[3]); + } + + this.encryptionType = arguments[0]; + this.cipherText = arguments[1]; + this.initializationVector = arguments[2] || null; + this.mac = arguments[3] || null; + + return; + } + else if (arguments.length !== 1) { + return; + } + + this.encryptedString = arguments[0]; + if (!this.encryptedString) { + return; + } + + var constants = chrome.extension.getBackgroundPage().constantsService; + + var headerPieces = this.encryptedString.split('.'), + encPieces; + + if (headerPieces.length === 2) { + try { + this.encryptionType = parseInt(headerPieces[0]); + encPieces = headerPieces[1].split('|'); + } + catch (e) { + return; + } + } + else { + encPieces = this.encryptedString.split('|'); + this.encryptionType = encPieces.length === 3 ? constants.encType.AesCbc128_HmacSha256_B64 : + constants.encType.AesCbc256_B64; + } + + switch (this.encryptionType) { + case constants.encType.AesCbc128_HmacSha256_B64: + case constants.encType.AesCbc256_HmacSha256_B64: + if (encPieces.length !== 3) { + return; + } + + this.initializationVector = encPieces[0]; + this.cipherText = encPieces[1]; + this.mac = encPieces[2]; + break; + case constants.encType.AesCbc256_B64: + if (encPieces.length !== 2) { + return; + } + + this.initializationVector = encPieces[0]; + this.cipherText = encPieces[1]; + break; + case constants.encType.Rsa2048_OaepSha256_B64: + case constants.encType.Rsa2048_OaepSha1_B64: + if (encPieces.length !== 1) { + return; + } + + this.cipherText = encPieces[0]; + break; + default: + return; } }; @@ -52,6 +126,9 @@ var Folder = function (obj, alreadyEncrypted) { cryptoService.decrypt(this).then(function (decValue) { this.decryptedValue = decValue; deferred.resolve(this.decryptedValue); + }, function () { + this.decryptedValue = '[error: cannot decrypt]'; + deferred.resolve(this.decryptedValue); }); } else { diff --git a/src/popup/app/config.js b/src/popup/app/config.js index 39af96b634..b6860ca5d2 100644 --- a/src/popup/app/config.js +++ b/src/popup/app/config.js @@ -14,7 +14,7 @@ var userService = $injector.get('userService'); var cryptoService = $injector.get('cryptoService'); - cryptoService.getKey(false, function (key) { + cryptoService.getKey(function (key) { userService.isAuthenticated(function (isAuthenticated) { if (isAuthenticated) { if (!key) { diff --git a/src/popup/app/lock/lockController.js b/src/popup/app/lock/lockController.js index ad3e2920ce..f6b42e67b6 100644 --- a/src/popup/app/lock/lockController.js +++ b/src/popup/app/lock/lockController.js @@ -24,7 +24,7 @@ userService.getEmail(function (email) { var key = cryptoService.makeKey($scope.masterPassword, email); cryptoService.hashPassword($scope.masterPassword, key, function (keyHash) { - cryptoService.getKeyHash(true, function (storedKeyHash) { + cryptoService.getKeyHash(function (storedKeyHash) { if (storedKeyHash && keyHash && storedKeyHash === keyHash) { cryptoService.setKey(key, function () { chrome.runtime.sendMessage({ command: 'unlocked' }); diff --git a/src/popup/app/settings/settingsController.js b/src/popup/app/settings/settingsController.js index e21b70cf49..cf8601a7de 100644 --- a/src/popup/app/settings/settingsController.js +++ b/src/popup/app/settings/settingsController.js @@ -26,7 +26,7 @@ } chrome.storage.local.set(obj, function () { - cryptoService.getKeyHash(false, function (keyHash) { + cryptoService.getKeyHash(function (keyHash) { if (keyHash) { cryptoService.toggleKey(function () { }); } diff --git a/src/popup/app/tools/toolsExportController.js b/src/popup/app/tools/toolsExportController.js index 4658779bfe..1a5d9eb1d3 100644 --- a/src/popup/app/tools/toolsExportController.js +++ b/src/popup/app/tools/toolsExportController.js @@ -22,7 +22,7 @@ userService.getEmail(function (email) { var key = cryptoService.makeKey($scope.masterPassword, email); cryptoService.hashPassword($scope.masterPassword, key, function (keyHash) { - cryptoService.getKeyHash(true, function (storedKeyHash) { + cryptoService.getKeyHash(function (storedKeyHash) { if (storedKeyHash && keyHash && storedKeyHash === keyHash) { deferred.resolve(); } diff --git a/src/services/apiService.js b/src/services/apiService.js index e217e16b7c..ffd87fe486 100644 --- a/src/services/apiService.js +++ b/src/services/apiService.js @@ -1,6 +1,6 @@ function ApiService(tokenService, appIdService, utilsService, logoutCallback) { - //this.baseUrl = 'http://localhost:4000'; - this.baseUrl = 'https://api.bitwarden.com'; + this.baseUrl = 'http://localhost:4000'; + //this.baseUrl = 'https://api.bitwarden.com'; this.tokenService = tokenService; this.logoutCallback = logoutCallback; this.appIdService = appIdService; diff --git a/src/services/constantsService.js b/src/services/constantsService.js index 94f7431f79..edfb7bb03c 100644 --- a/src/services/constantsService.js +++ b/src/services/constantsService.js @@ -4,6 +4,13 @@ function ConstantsService() { disableAddLoginNotificationKey: 'disableAddLoginNotification', disableContextMenuItemKey: 'disableContextMenuItem', lockOptionKey: 'lockOption', - lastActiveKey: 'lastActive' + lastActiveKey: 'lastActive', + encType: { + AesCbc256_B64: 0, + AesCbc128_HmacSha256_B64: 1, + AesCbc256_HmacSha256_B64: 2, + Rsa2048_OaepSha256_B64: 3, + Rsa2048_OaepSha1_B64: 4 + } }; }; diff --git a/src/services/cryptoService.js b/src/services/cryptoService.js index b521bf24dc..3b058b5a0b 100644 --- a/src/services/cryptoService.js +++ b/src/services/cryptoService.js @@ -1,15 +1,13 @@ function CryptoService(constantsService) { this.constantsService = constantsService; - initCryptoService(); + initCryptoService(constantsService); }; -function initCryptoService() { +function initCryptoService(constantsService) { var _key, - _b64Key, + _legacyEtmKey, _keyHash, - _b64KeyHash, - _encKey, - _macKey; + _b64KeyHash; CryptoService.prototype.setKey = function (key, callback) { if (!callback || typeof callback !== 'function') { @@ -27,7 +25,7 @@ function initCryptoService() { } chrome.storage.local.set({ - 'key': forge.util.encode64(key) + 'key': key.keyB64 }, function () { callback(); }); @@ -39,33 +37,24 @@ function initCryptoService() { throw 'callback function required'; } - _keyHash = forge.util.encode64(keyHash); + _keyHash = keyHash; chrome.storage.local.set({ - 'keyHash': keyHash + 'keyHash': _keyHash }, function () { callback(); }); } - CryptoService.prototype.getKey = function (b64, callback) { + CryptoService.prototype.getKey = function (callback) { if (!callback || typeof callback !== 'function') { throw 'callback function required'; } - if (b64 && b64 === true && _b64Key) { - callback(_b64Key); - return; - } - else if (!b64 && _key) { + if (_key) { callback(_key); return; } - else if (b64 && b64 === true && _key && !_b64Key) { - _b64Key = forge.util.encode64(_key); - callback(_b64Key); - return; - } var self = this; chrome.storage.local.get(self.constantsService.lockOptionKey, function (obj) { @@ -77,13 +66,7 @@ function initCryptoService() { chrome.storage.local.get('key', function (obj) { if (obj && obj.key) { - _key = forge.util.decode64(obj.key); - - if (b64 && b64 === true) { - _b64Key = obj.key; - callback(_b64Key); - return; - } + _key = new CryptoKey(obj.key, true); } callback(_key); @@ -91,67 +74,19 @@ function initCryptoService() { }); }; - CryptoService.prototype.getEncKey = function (callback) { + CryptoService.prototype.getKeyHash = function (callback) { if (!callback || typeof callback !== 'function') { throw 'callback function required'; } - if (_encKey) { - callback(_encKey); - } - - this.getKey(false, function (key) { - var buffer = forge.util.createBuffer(key); - _encKey = buffer.getBytes(16); - callback(_encKey); - }); - }; - - CryptoService.prototype.getMacKey = function (callback) { - if (!callback || typeof callback !== 'function') { - throw 'callback function required'; - } - - if (_macKey) { - callback(_macKey); - } - - this.getKey(false, function (key) { - var buffer = forge.util.createBuffer(key); - buffer.getBytes(16); - _macKey = buffer.getBytes(16); - callback(_macKey); - }); - }; - - CryptoService.prototype.getKeyHash = function (b64, callback) { - if (!callback || typeof callback !== 'function') { - throw 'callback function required'; - } - - if (b64 && b64 === true && _b64KeyHash) { - callback(_b64KeyHash); - return; - } - else if (!b64 && _keyHash) { + if (_keyHash) { callback(_keyHash); return; } - else if (b64 && b64 === true && _keyHash && !_b64KeyHash) { - _b64KeyHash = forge.util.encode64(_keyHash); - callback(_b64KeyHash); - return; - } chrome.storage.local.get('keyHash', function (obj) { if (obj && obj.keyHash) { - _keyHash = forge.util.decode64(obj.keyHash); - - if (b64 && b64 === true) { - _b64KeyHash = obj.keyHash; - callback(_b64KeyHash); - return; - } + _keyHash = obj.keyHash; } callback(_keyHash); @@ -163,7 +98,7 @@ function initCryptoService() { throw 'callback function required'; } - _key = _b64Key = _macKey = _encKey = null; + _key = _legacyEtmKey = null; chrome.storage.local.remove('key', function () { callback(); }); @@ -174,7 +109,7 @@ function initCryptoService() { throw 'callback function required'; } - _keyHash = _b64KeyHash = null; + _keyHash = null; chrome.storage.local.remove('keyHash', function () { callback(); }); @@ -186,7 +121,7 @@ function initCryptoService() { } var self = this; - self.getKey(false, function (key) { + self.getKey(function (key) { chrome.storage.local.get(self.constantsService.lockOptionKey, function (obj) { if (obj && (obj[self.constantsService.lockOptionKey] || obj[self.constantsService.lockOptionKey] === 0)) { // if we have a lock option set, clear the key @@ -207,79 +142,62 @@ function initCryptoService() { }); }; - CryptoService.prototype.makeKey = function (password, salt, b64) { - var key = forge.pbkdf2(forge.util.encodeUtf8(password), forge.util.encodeUtf8(salt), + CryptoService.prototype.makeKey = function (password, salt) { + var keyBytes = forge.pbkdf2(forge.util.encodeUtf8(password), forge.util.encodeUtf8(salt), 5000, 256 / 8, 'sha256'); - if (b64 && b64 === true) { - return forge.util.encode64(key); - } - - return key; + return new CryptoKey(keyBytes); }; CryptoService.prototype.hashPassword = function (password, key, callback) { - this.getKey(false, function (storedKey) { - if (!key) { - key = storedKey; - } + this.getKey(function (storedKey) { + key = key || storedKey; if (!password || !key) { throw 'Invalid parameters.'; } - var hashBits = forge.pbkdf2(key, forge.util.encodeUtf8(password), 1, 256 / 8, 'sha256'); + var hashBits = forge.pbkdf2(key.key, forge.util.encodeUtf8(password), 1, 256 / 8, 'sha256'); callback(forge.util.encode64(hashBits)); }); }; - CryptoService.prototype.encrypt = function (plaintextValue) { + CryptoService.prototype.encrypt = function (plainValue, key, plainValueEncoding) { var self = this; var deferred = Q.defer(); - if (plaintextValue === null || plaintextValue === undefined) { + if (plainValue === null || plainValue === undefined) { deferred.resolve(null); } else { - self.getKey(false, function (key) { - self.getEncKey(function (theEncKey) { - self.getMacKey(function (macKey) { - if (!key || !theEncKey || !macKey) { - throw 'Encryption key unavailable.'; - } + self.getKey(function (localKey) { + key = key || localKey; + if (!key) { + throw 'Encryption key unavailable.'; + } - // TODO: Turn on whenever ready to support encrypt-then-mac - var encKey = false ? theEncKey : key; + plainValueEncoding = plainValueEncoding || 'utf8'; + var buffer = forge.util.createBuffer(plainValue, plainValueEncoding); + var ivBytes = forge.random.getBytesSync(16); + var cipher = forge.cipher.createCipher('AES-CBC', key.encKey); + cipher.start({ iv: ivBytes }); + cipher.update(buffer); + cipher.finish(); - var buffer = forge.util.createBuffer(plaintextValue, 'utf8'); - var ivBytes = forge.random.getBytesSync(16); - var cipher = forge.cipher.createCipher('AES-CBC', encKey); - cipher.start({ iv: ivBytes }); - cipher.update(buffer); - cipher.finish(); + var iv = forge.util.encode64(ivBytes); + var ctBytes = cipher.output.getBytes(); + var ct = forge.util.encode64(ctBytes); + var mac = !key.macKey ? null : computeMac(ctBytes, ivBytes, key.macKey); - var iv = forge.util.encode64(ivBytes); - var ctBytes = cipher.output.getBytes(); - var ct = forge.util.encode64(ctBytes); - var cipherString = iv + '|' + ct; - - // TODO: Turn on whenever ready to support encrypt-then-mac - if (false) { - var mac = computeMac(ctBytes, ivBytes, macKey); - cipherString = cipherString + '|' + mac; - } - - var cs = new CipherString(cipherString); - deferred.resolve(cs); - }); - }); + var cs = new CipherString(key.encType, iv, ct, mac); + deferred.resolve(cs); }); } return deferred.promise; }; - CryptoService.prototype.decrypt = function (cipherString) { + CryptoService.prototype.decrypt = function (cipherString, key, outputEncoding) { var deferred = Q.defer(); var self = this; @@ -287,35 +205,56 @@ function initCryptoService() { throw 'cannot decrypt nothing'; } - self.getKey(false, function (key) { - self.getEncKey(function (theEncKey) { - self.getMacKey(function (macKey) { - if (!macKey) { - throw 'MAC key unavailable.'; - } + self.getKey(function (localKey) { + key = key || localKey; + if (!key) { + throw 'Encryption key unavailable.'; + } - var ivBytes = forge.util.decode64(cipherString.initializationVector); - var ctBytes = forge.util.decode64(cipherString.cipherText); + outputEncoding = outputEncoding || 'utf8'; - var computedMac = null; - if (cipherString.mac) { - computedMac = computeMac(ctBytes, ivBytes, macKey); - if (computedMac !== cipherString.mac) { - console.error('MAC failed.'); - deferred.reject('MAC failed.'); - } - } + if (cipherString.encryptionType === constantsService.encType.AesCbc128_HmacSha256_B64 && + key.encType === constantsService.encType.AesCbc256_B64) { + // Old encrypt-then-mac scheme, swap out the key + _legacyEtmKey = _legacyEtmKey || + new CryptoKey(key.key, false, constantsService.encType.AesCbc128_HmacSha256_B64); + key = _legacyEtmKey; + } - var ctBuffer = forge.util.createBuffer(ctBytes); - var decipher = forge.cipher.createDecipher('AES-CBC', computedMac ? theEncKey : key); - decipher.start({ iv: ivBytes }); - decipher.update(ctBuffer); - decipher.finish(); + if (cipherString.encryptionType !== key.encType) { + throw 'encType unavailable.'; + } - var decValue = decipher.output.toString('utf8'); - deferred.resolve(decValue); - }); - }); + var ivBytes = forge.util.decode64(cipherString.initializationVector); + var ctBytes = forge.util.decode64(cipherString.cipherText); + + if (key.macKey && cipherString.mac) { + var computedMac = computeMac(ctBytes, ivBytes, key.macKey); + if (computedMac !== cipherString.mac) { + console.error('MAC failed.'); + deferred.reject('MAC failed.'); + } + } + + try { + var ctBuffer = forge.util.createBuffer(ctBytes); + var decipher = forge.cipher.createDecipher('AES-CBC', key.encKey); + decipher.start({ iv: ivBytes }); + decipher.update(ctBuffer); + decipher.finish(); + + var decValue; + if (outputEncoding === 'utf8') { + decValue = decipher.output.toString('utf8'); + } + else { + decValue = decipher.output.getBytes(); + } + deferred.resolve(decValue); + } + catch (e) { + deferred.reject('Decryption failed.'); + } }); return deferred.promise; @@ -328,4 +267,52 @@ function initCryptoService() { var mac = hmac.digest(); return forge.util.encode64(mac.getBytes()); } + + function CryptoKey(keyBytes, b64KeyBytes, encType) { + if (b64KeyBytes) { + keyBytes = forge.util.decode64(keyBytes); + } + + if (!keyBytes) { + throw 'Must provide keyBytes'; + } + + var buffer = forge.util.createBuffer(keyBytes); + if (!buffer || buffer.length() === 0) { + throw 'Couldn\'t make buffer'; + } + var bufferLength = buffer.length(); + + if (encType === null || encType === undefined) { + if (bufferLength === 32) { + encType = constantsService.encType.AesCbc256_B64; + } + else if (bufferLength === 64) { + encType = constantsService.encType.AesCbc256_HmacSha256_B64; + } + else { + throw 'Unable to determine encType.'; + } + } + + this.key = keyBytes; + this.keyB64 = forge.util.encode64(keyBytes); + this.encType = encType; + + if (encType === constantsService.encType.AesCbc256_B64 && bufferLength === 32) { + this.encKey = keyBytes; + this.macKey = null; + } + else if (encType === constantsService.encType.AesCbc128_HmacSha256_B64 && bufferLength === 32) { + this.encKey = buffer.getBytes(16); // first half + this.macKey = buffer.getBytes(16); // second half + } + else if (encType === constantsService.encType.AesCbc256_HmacSha256_B64 && bufferLength === 64) { + this.encKey = buffer.getBytes(32); // first half + this.macKey = buffer.getBytes(32); // second half + } + else { + throw 'Unsupported encType/key length.'; + } + } }; diff --git a/src/services/folderService.js b/src/services/folderService.js index 7622188f3d..b02b296ef9 100644 --- a/src/services/folderService.js +++ b/src/services/folderService.js @@ -68,7 +68,7 @@ function initFolderService() { var deferred = Q.defer(); var self = this; - cryptoService.getKey(false, function (key) { + cryptoService.getKey(function (key) { if (!key) { deferred.reject(); return; diff --git a/src/services/loginService.js b/src/services/loginService.js index 2c45cef6c3..f203b1cde0 100644 --- a/src/services/loginService.js +++ b/src/services/loginService.js @@ -86,7 +86,7 @@ function initLoginService() { var deferred = Q.defer(); var self = this; - cryptoService.getKey(false, function (key) { + cryptoService.getKey(function (key) { if (!key) { deferred.reject(); return;