support user encryption key

This commit is contained in:
Kyle Spearrin 2017-06-02 00:10:29 -04:00
parent e282966d64
commit 16098a1743
13 changed files with 218 additions and 104 deletions

View File

@ -11,7 +11,8 @@ var userService = new UserService(tokenService, apiService, cryptoService);
var settingsService = new SettingsService(userService);
var loginService = new LoginService(cryptoService, userService, apiService, settingsService);
var folderService = new FolderService(cryptoService, userService, apiService);
var syncService = new SyncService(loginService, folderService, userService, apiService, settingsService, cryptoService);
var syncService = new SyncService(loginService, folderService, userService, apiService, settingsService,
cryptoService, logout);
var autofillService = new AutofillService();
var passwordGenerationService = new PasswordGenerationService();
@ -86,7 +87,7 @@ chrome.runtime.onMessage.addListener(function (msg, sender, sendResponse) {
setIcon();
function setIcon() {
userService.isAuthenticated(function (isAuthenticated) {
cryptoService.getKey(function (key) {
cryptoService.getKey().then(function (key) {
var suffix = '';
if (!isAuthenticated) {
suffix = '_gray';
@ -668,7 +669,7 @@ function logout(expired, callback) {
settingsService.clear(function () {
tokenService.clearToken(function () {
cryptoService.clearKeys(function () {
userService.clearUserIdAndEmail(function () {
userService.clear(function () {
loginService.clear(userId, function () {
folderService.clear(userId, function () {
chrome.runtime.sendMessage({
@ -757,7 +758,7 @@ function checkLock() {
return;
}
cryptoService.getKey(function (key) {
cryptoService.getKey().then(function (key) {
if (!key) {
// no key so no need to lock
return;

View File

@ -48,11 +48,12 @@ var TokenRequest = function (email, masterPasswordHash, token, device) {
};
};
var RegisterRequest = function (email, masterPasswordHash, masterPasswordHint) {
var RegisterRequest = function (email, masterPasswordHash, masterPasswordHint, key) {
this.name = null;
this.email = email;
this.masterPasswordHash = masterPasswordHash;
this.masterPasswordHint = masterPasswordHint ? masterPasswordHint : null;
this.key = key;
};
var PasswordHintRequest = function (email) {

View File

@ -38,6 +38,9 @@ var ProfileResponse = function (response) {
this.masterPasswordHint = response.MasterPasswordHint;
this.culture = response.Culture;
this.twoFactorEnabled = response.TwoFactorEnabled;
this.key = response.Key;
this.privateKey = response.PrivateKey;
this.securityStamp = response.SecurityStamp;
this.organizations = [];
if (response.Organizations) {
@ -68,6 +71,7 @@ var IdentityTokenResponse = function (response) {
this.tokenType = response.token_type;
this.privateKey = response.PrivateKey;
this.key = response.Key;
};
var ListResponse = function (data) {

View File

@ -9,13 +9,8 @@ var CipherString = function () {
var constants = chrome.extension.getBackgroundPage().constantsService;
if (arguments.length >= 2) {
// ct and optional header
if (arguments[0] === constants.encType.AesCbc256_B64) {
this.encryptedString = arguments[1];
}
else {
this.encryptedString = arguments[0] + '.' + arguments[1];
}
// ct and header
this.encryptedString = arguments[0] + '.' + arguments[1];
// iv
if (arguments.length > 2 && arguments[2]) {

View File

@ -2,8 +2,8 @@
.module('bit.accounts')
.controller(
'accountsRegisterController',
function ($scope, $state, cryptoService, toastr, $q, apiService, utilsService, $analytics, i18nService) {
'accountsRegisterController',
function ($scope, $state, cryptoService, toastr, $q, apiService, utilsService, $analytics, i18nService) {
$scope.i18n = i18nService;
$scope.model = {};
@ -45,15 +45,17 @@
function registerPromise(key, masterPassword, email, hint) {
return $q(function (resolve, reject) {
cryptoService.hashPassword(masterPassword, key, function (hashedPassword) {
var request = new RegisterRequest(email, hashedPassword, hint);
apiService.postRegister(request,
function () {
resolve();
},
function (error) {
reject(error);
});
cryptoService.makeEncKey(key).then(function (encKey) {
cryptoService.hashPassword(masterPassword, key, function (hashedPassword) {
var request = new RegisterRequest(email, hashedPassword, hint, encKey.encryptedString);
apiService.postRegister(request,
function () {
resolve();
},
function (error) {
reject(error);
});
});
});
});
}

View File

@ -14,7 +14,7 @@
var userService = $injector.get('userService');
var cryptoService = $injector.get('cryptoService');
cryptoService.getKey(function (key) {
cryptoService.getKey().then(function (key) {
userService.isAuthenticated(function (isAuthenticated) {
if (isAuthenticated) {
if (!key) {

View File

@ -24,12 +24,9 @@
cryptoService.setKey(key, function () {
cryptoService.setKeyHash(hashedPassword, function () {
userService.setUserIdAndEmail(tokenService.getUserId(), tokenService.getEmail(), function () {
if (!response.privateKey) {
loggedIn(deferred);
return;
}
cryptoService.setEncPrivateKey(response.privateKey).then(function () {
cryptoService.setEncKey(response.key).then(function () {
return cryptoService.setEncPrivateKey(response.privateKey);
}).then(function () {
loggedIn(deferred);
});
});

View File

@ -4,16 +4,16 @@ function ApiService(tokenService, appIdService, utilsService, logoutCallback) {
//this.identityBaseUrl = 'http://localhost:33656';
// Desktop external
//this.baseUrl = 'http://192.168.1.8:4000';
//this.identityBaseUrl = 'http://192.168.1.8:33656';
this.baseUrl = 'http://192.168.1.6:4000';
this.identityBaseUrl = 'http://192.168.1.6:33656';
// Preview
//this.baseUrl = 'https://preview-api.bitwarden.com';
//this.identityBaseUrl = 'https://preview-identity.bitwarden.com';
// Production
this.baseUrl = 'https://api.bitwarden.com';
this.identityBaseUrl = 'https://identity.bitwarden.com';
//this.baseUrl = 'https://api.bitwarden.com';
//this.identityBaseUrl = 'https://identity.bitwarden.com';
this.tokenService = tokenService;
this.logoutCallback = logoutCallback;

View File

@ -5,6 +5,7 @@ function CryptoService(constantsService) {
function initCryptoService(constantsService) {
var _key,
_encKey,
_legacyEtmKey,
_keyHash,
_privateKey,
@ -47,12 +48,26 @@ function initCryptoService(constantsService) {
});
}
CryptoService.prototype.setEncKey = function (encKey) {
var deferred = Q.defer();
chrome.storage.local.set({
'encKey': encKey
}, function () {
_encKey = null;
deferred.resolve();
});
return deferred.promise;
}
CryptoService.prototype.setEncPrivateKey = function (encPrivateKey) {
var deferred = Q.defer();
chrome.storage.local.set({
'encPrivateKey': encPrivateKey
}, function () {
_privateKey = null;
deferred.resolve();
});
@ -76,21 +91,19 @@ function initCryptoService(constantsService) {
return deferred.promise;
}
CryptoService.prototype.getKey = function (callback) {
if (!callback || typeof callback !== 'function') {
throw 'callback function required';
}
CryptoService.prototype.getKey = function () {
var deferred = Q.defer();
if (_key) {
callback(_key);
return;
deferred.resolve(_key);
return deferred.promise;
}
var self = this;
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, we do not try to fetch the storage key since it should not even be there
callback(null);
deferred.resolve(null);
return;
}
@ -99,9 +112,11 @@ function initCryptoService(constantsService) {
_key = new SymmetricCryptoKey(obj.key, true);
}
callback(_key);
deferred.resolve(_key);
});
});
return deferred.promise;
};
CryptoService.prototype.getKeyHash = function (callback) {
@ -123,6 +138,33 @@ function initCryptoService(constantsService) {
});
};
CryptoService.prototype.getEncKey = function () {
var deferred = Q.defer();
if (_encKey) {
deferred.resolve(_encKey);
return deferred.promise;
}
var self = this;
chrome.storage.local.get('encKey', function (obj) {
if (!obj || !obj.encKey) {
deferred.resolve(null);
return;
}
self.getKey().then(function (key) {
return self.decrypt(new CipherString(obj.encKey), key, 'raw');
}).then(function (encKey) {
_encKey = new SymmetricCryptoKey(encKey);
deferred.resolve(_encKey);
}, function () {
deferred.reject('Cannot get enc key. Decryption failed.');
});
});
return deferred.promise;
};
CryptoService.prototype.getPrivateKey = function () {
var deferred = Q.defer();
if (_privateKey) {
@ -232,8 +274,26 @@ function initCryptoService(constantsService) {
return deferred.promise;
};
CryptoService.prototype.clearEncKey = function () {
var deferred = Q.defer();
_encKey = null;
chrome.storage.local.remove('encKey', function () {
deferred.resolve();
});
return deferred.promise;
};
CryptoService.prototype.clearPrivateKey = function () {
var deferred = Q.defer();
_privateKey = null;
chrome.storage.local.remove('encPrivateKey', function () {
deferred.resolve();
});
return deferred.promise;
};
CryptoService.prototype.clearOrgKeys = function (memoryOnly) {
@ -258,8 +318,13 @@ function initCryptoService(constantsService) {
}
var self = this;
Q.all([self.clearKey(), self.clearKeyHash(), self.clearOrgKeys()]).then(function () {
self.clearPrivateKey();
Q.all([
self.clearKey(),
self.clearKeyHash(),
self.clearOrgKeys(),
self.clearEncKey(),
self.clearPrivateKey()
]).then(function () {
callback();
});
};
@ -270,7 +335,7 @@ function initCryptoService(constantsService) {
}
var self = this;
self.getKey(function (key) {
self.getKey().then(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
@ -299,7 +364,7 @@ function initCryptoService(constantsService) {
};
CryptoService.prototype.hashPassword = function (password, key, callback) {
this.getKey(function (storedKey) {
this.getKey().then(function (storedKey) {
key = key || storedKey;
if (!password || !key) {
@ -311,6 +376,11 @@ function initCryptoService(constantsService) {
});
};
CryptoService.prototype.makeEncKey = function (key) {
var bytes = forge.random.getBytesSync(512 / 8);
return this.encrypt(bytes, key, 'raw');
};
CryptoService.prototype.encrypt = function (plainValue, key, plainValueEncoding) {
var self = this;
var deferred = Q.defer();
@ -319,8 +389,8 @@ function initCryptoService(constantsService) {
deferred.resolve(null);
}
else {
self.getKey(function (localKey) {
key = key || localKey;
getKeyForEncryption(self, key).then(function (keyToUse) {
key = keyToUse;
if (!key) {
deferred.reject('Encryption key unavailable.');
return;
@ -356,8 +426,8 @@ function initCryptoService(constantsService) {
return;
}
self.getKey(function (localKey) {
key = key || localKey;
getKeyForEncryption(self, key).then(function (keyToUse) {
key = keyToUse;
if (!key) {
deferred.reject('Encryption key unavailable.');
return;
@ -479,6 +549,23 @@ function initCryptoService(constantsService) {
return b64Output ? forge.util.encode64(mac.getBytes()) : mac.getBytes();
}
function getKeyForEncryption(self, key) {
var deferred = Q.defer();
if (key) {
deferred.resolve(key);
}
else {
self.getEncKey().then(function (encKey) {
return encKey || self.getKey();
}).then(function (keyToUse) {
deferred.resolve(keyToUse);
});
}
return deferred.promise;
}
// Safely compare two MACs in a way that protects against timing attacks (Double HMAC Verification).
// ref: https://www.nccgroup.trust/us/about-us/newsroom-and-events/blog/2011/february/double-hmac-verification/
function macsEqual(macKey, mac1, mac2) {

View File

@ -68,7 +68,7 @@ function initFolderService() {
var deferred = Q.defer();
var self = this;
cryptoService.getKey(function (key) {
cryptoService.getKey().then(function (key) {
if (!key) {
deferred.reject();
return;

View File

@ -91,7 +91,7 @@ function initLoginService() {
var deferred = Q.defer();
var self = this;
cryptoService.getKey(function (key) {
cryptoService.getKey().then(function (key) {
if (!key) {
deferred.reject();
return;

View File

@ -1,11 +1,13 @@
function SyncService(loginService, folderService, userService, apiService, settingsService, cryptoService) {
function SyncService(loginService, folderService, userService, apiService, settingsService,
cryptoService, logoutCallback) {
this.loginService = loginService;
this.folderService = folderService;
this.userService = userService;
this.apiService = apiService;
this.settingsService = settingsService;
this.cryptoService = settingsService;
this.cryptoService = cryptoService;
this.syncInProgress = false;
this.logoutCallback = logoutCallback;
initSyncService();
};
@ -37,12 +39,12 @@ function initSyncService() {
return;
}
syncProfile().then(function () {
return syncFolders(userId);
syncProfile(self).then(function () {
return syncFolders(self, userId);
}).then(function () {
return syncCiphers(userId);
return syncCiphers(self, userId);
}).then(function () {
return syncSettings(userId);
return syncSettings(self, userId);
}).then(function () {
self.setLastSync(now, function () {
self.syncCompleted(true);
@ -80,45 +82,37 @@ function initSyncService() {
});
}
function syncProfile() {
function syncProfile(self) {
var deferred = Q.defer();
var self = this;
function setKeys(hasPrivateKey, response, d) {
if (response.organizations && response.organizations.length && !hasPrivateKey) {
self.apiService.getKeys(function (keysResponse) {
if (keysResponse.privateKey) {
self.cryptoService.setEncPrivateKey(keysResponse.privateKey).then(function () {
return self.cryptoService.setOrgKeys(response.organizations);
}, function () {
d.reject();
}).then(function () {
d.resolve();
}, function () {
d.reject();
});
}
else {
d.resolve();
}
}, function () {
d.reject();
});
}
else {
self.cryptoService.setOrgKeys(response.organizations).then(function () {
d.resolve();
}, function () {
d.reject();
});
}
}
self.apiService.getProfile(function (response) {
self.cryptoService.getPrivateKey().then(function (privateKey) {
setKeys(!!privateKey, response, deferred);
self.userService.getSecurityStamp().then(function (stamp) {
if (stamp && stamp != response.securityStamp) {
if (self.logoutCallback) {
self.logoutCallback(true, function () { });
}
deferred.reject();
return;
}
return self.cryptoService.setEncKey(response.key);
}).then(function () {
return self.cryptoService.setEncPrivateKey(response.privateKey);
}, function () {
setKeys(true, response, deferred);
deferred.reject();
}).then(function () {
return self.cryptoService.setOrgKeys(response.organizations);
}, function () {
deferred.reject();
}).then(function () {
return self.userService.setSecurityStamp(response.securityStamp);
}, function () {
deferred.reject();
}).then(function () {
deferred.resolve();
}, function () {
deferred.reject();
});
}, function () {
deferred.reject();
@ -127,9 +121,8 @@ function initSyncService() {
return deferred.promise
}
function syncFolders(userId) {
function syncFolders(self, userId) {
var deferred = Q.defer();
var self = this;
self.apiService.getFolders(function (response) {
var folders = {};
@ -148,9 +141,8 @@ function initSyncService() {
return deferred.promise
}
function syncCiphers(userId) {
function syncCiphers(self, userId) {
var deferred = Q.defer();
var self = this;
self.apiService.getCiphers(function (response) {
var logins = {};
@ -172,9 +164,8 @@ function initSyncService() {
return deferred.promise
}
function syncSettings(userId) {
function syncSettings(self, userId) {
var deferred = Q.defer();
var self = this;
var ciphers = self.apiService.getIncludedDomains(function (response) {
var eqDomains = [];

View File

@ -8,10 +8,12 @@
function initUserService() {
var userIdKey = 'userId',
userEmailKey = 'userEmail';
userEmailKey = 'userEmail',
stampKey = 'securityStamp';
var _userId = null,
_email = null;
_email = null,
_stamp = null;
UserService.prototype.setUserIdAndEmail = function (userId, email, callback) {
if (!callback || typeof callback !== 'function') {
@ -33,6 +35,20 @@ function initUserService() {
});
};
UserService.prototype.setSecurityStamp = function (stamp) {
var deferred = Q.defer();
_stamp = stamp;
var stampObj = {};
stampObj[stampKey] = stamp;
chrome.storage.local.set(stampObj, function () {
deferred.resolve();
});
return deferred.promise
};
UserService.prototype.getUserId = function (callback) {
if (!callback || typeof callback !== 'function') {
throw 'callback function required';
@ -69,15 +85,35 @@ function initUserService() {
});
};
UserService.prototype.clearUserIdAndEmail = function (callback) {
UserService.prototype.getSecurityStamp = function () {
var deferred = Q.defer();
if (_stamp) {
deferred.resolve(_stamp);
}
chrome.storage.local.get(stampKey, function (obj) {
if (obj && obj[stampKey]) {
_stamp = obj[stampKey];
}
deferred.resolve(_stamp);
});
return deferred.promise
};
UserService.prototype.clear = function (callback) {
if (!callback || typeof callback !== 'function') {
throw 'callback function required';
}
_userId = _email = null;
_userId = _email = _stamp = null;
chrome.storage.local.remove(userIdKey, function () {
chrome.storage.local.remove(userEmailKey, function () {
callback();
chrome.storage.local.remove(stampKey, function () {
callback();
});
});
});
};