This commit is contained in:
Mo Bitar
2018-05-15 12:06:14 -05:00
parent ff9993717c
commit 8307fd4446
6 changed files with 65 additions and 84 deletions

View File

@@ -296,7 +296,7 @@ class AccountMenu {
}.bind(this)
if(data.auth_params) {
SFJS.crypto.computeEncryptionKeysForUser(_.merge({password: password}, data.auth_params), function(keys){
SFJS.crypto.computeEncryptionKeysForUser(password, data.auth_params, (keys) => {
try {
SFItemTransformer.decryptMultipleItems(data.items, keys, false); /* throws = false as we don't want to interrupt all decryption if just one fails */
// delete items enc_item_key since the user's actually key will do the encrypting once its passed off
@@ -323,7 +323,7 @@ class AccountMenu {
callback(null);
return;
}
}.bind(this));
});
} else {
onDataReady();
}
@@ -468,30 +468,6 @@ class AccountMenu {
return keys && !keys.ak;
}
$scope.clickedSecurityUpdate = function() {
if(!$scope.securityUpdateData) {
$scope.securityUpdateData = {};
}
$scope.securityUpdateData.showForm = true;
}
$scope.submitSecurityUpdateForm = function() {
$scope.securityUpdateData.processing = true;
var authParams = authManager.getAuthParams();
SFJS.crypto.computeEncryptionKeysForUser(_.merge({password: $scope.securityUpdateData.password}, authParams), function(keys){
if(keys.mk !== authManager.keys().mk) {
alert("Invalid password. Please try again.");
$timeout(function(){
$scope.securityUpdateData.processing = false;
})
return;
}
authManager.saveKeys(keys);
});
}
/*
Encryption Status

View File

@@ -3,7 +3,7 @@ class ItemParams {
constructor(item, keys, version) {
this.item = item;
this.keys = keys;
this.version = version || "002";
this.version = version || SFJS.crypto.version();
}
paramsForExportFile(includeDeleted) {
@@ -36,7 +36,7 @@ class ItemParams {
var params = {uuid: this.item.uuid, content_type: this.item.content_type, deleted: this.item.deleted, created_at: this.item.created_at};
if(!this.item.errorDecrypting) {
// Items should always be encrypted for export files. Only respect item.doNotEncrypt for remote sync params;
// Items should always be encrypted for export files. Only respect item.doNotEncrypt for remote sync params.
var doNotEncrypt = this.item.doNotEncrypt() && !this.forExportFile;
if(this.keys && !doNotEncrypt) {
var encryptedParams = SFItemTransformer.encryptItem(this.item, this.keys, this.version);

View File

@@ -8,7 +8,7 @@ angular.module('app')
}
this.$get = function($rootScope, $timeout, httpManager, modelManager, dbManager, storageManager, singletonManager) {
return new AuthManager($rootScope, $timeout, httpManager, modelManager, dbManager, storageManager, singletonManager);
return new AuthManager($rootScope, $timeout, httpManager, modelManager, dbManager, storageManager, singletonManager);
}
function AuthManager($rootScope, $timeout, httpManager, modelManager, dbManager, storageManager, singletonManager) {
@@ -77,7 +77,7 @@ angular.module('app')
var keys = this.keys();
if(keys && keys.ak) {
return "002";
return "3";
} else {
return "001";
}
@@ -86,14 +86,25 @@ angular.module('app')
this.costMinimumForVersion = function(version) {
// all current versions have a min of 3000
// future versions will increase this
return 3000;
return SFJS.crypto.costMinimumForVersion(version);
}
this.isProtocolVersionSupported = function(version) {
var supportedVersions = ["001", "002"];
var supportedVersions = ["001", "002", "3"];
return supportedVersions.includes(version);
}
/* Upon sign in to an outdated version, the user will be presented with an alert requiring them to confirm
understanding they are signing in with an older version of the protocol, and must upgrade immediately after completing sign in.
*/
this.isProtocolVersionOutdated = function(version) {
return ["001"].includes(version);
}
this.supportsPasswordDerivationCost = function(cost) {
return SFJS.crypto.supportsPasswordDerivationCost(cost);
}
this.getAuthParamsForEmail = function(url, email, extraParams, callback) {
var requestUrl = url + "/auth/params";
httpManager.getAbsolute(requestUrl, _.merge({email: email}, extraParams), function(response){
@@ -107,17 +118,12 @@ angular.module('app')
})
}
this.supportsPasswordDerivationCost = function(cost) {
// some passwords are created on platforms with stronger pbkdf2 capabilities, like iOS,
// which accidentally used 60,000 iterations (now adjusted), which CryptoJS can't handle here (WebCrypto can however).
// if user has high password cost and is using browser that doesn't support WebCrypto,
// we want to tell them that they can't login with this browser.
return SFJS.crypto.supportsPasswordDerivationCost(cost);
}
this.login = function(url, email, password, ephemeral, extraParams, callback) {
this.getAuthParamsForEmail(url, email, extraParams, function(authParams){
// SF3 requires a unique identifier in the auth params
authParams.identifier = email;
if(authParams.error) {
callback(authParams);
return;
@@ -129,11 +135,18 @@ angular.module('app')
}
if(!this.isProtocolVersionSupported(authParams.version)) {
let message = "The protocol version associated with your account is outdated and no longer supported by this application. Please visit standardnotes.org/help/security-update for more information.";
let message = "The protocol version associated with your account is outdated and no longer supported by this application. Please visit standardnotes.org/help/security for more information.";
callback({error: {message: message}});
return;
}
if(this.isProtocolVersionOutdated(authParams.version)) {
let message = `The encryption version for your account, ${authParams.version}, is outdated. You may proceed with login, but are advised to follow prompts for Security Updates once inside. Please visit standardnotes.org/help/security for more information.`
if(!confirm(message)) {
return;
}
}
if(!this.supportsPasswordDerivationCost(authParams.pw_cost)) {
let message = "Your account was created on a platform with higher security capabilities than this browser supports. " +
"If we attempted to generate your login keys here, it would take hours. " +
@@ -144,13 +157,13 @@ angular.module('app')
var minimum = this.costMinimumForVersion(authParams.version);
if(authParams.pw_cost < minimum) {
let message = "Unable to login due to insecure password parameters. Please visit standardnotes.org/help/password-upgrade for more information.";
let message = "Unable to login due to insecure password parameters. Please visit standardnotes.org/help/security for more information.";
callback({error: {message: message}});
return;
}
SFJS.crypto.computeEncryptionKeysForUser(_.merge({password: password}, authParams), function(keys){
SFJS.crypto.computeEncryptionKeysForUser(password, authParams, function(keys){
console.log("Signing in with params", authParams, keys);
var requestUrl = url + "/auth/sign_in";
var params = _.merge({password: keys.pw, email: email}, extraParams);
httpManager.postAbsolute(requestUrl, params, function(response){
@@ -191,40 +204,40 @@ angular.module('app')
this.saveKeys = function(keys) {
this._keys = keys;
// Doesn't need to be saved.
// pw doesn't need to be saved.
// storageManager.setItem("pw", keys.pw);
storageManager.setItem("mk", keys.mk);
storageManager.setItem("ak", keys.ak);
}
this.register = function(url, email, password, ephemeral, callback) {
SFJS.crypto.generateInitialEncryptionKeysForUser({password: password, email: email}, function(keys, authParams){
SFJS.crypto.generateInitialEncryptionKeysForUser(email, password, (keys, authParams) => {
var requestUrl = url + "/auth";
var params = _.merge({password: keys.pw, email: email}, authParams);
httpManager.postAbsolute(requestUrl, params, function(response){
httpManager.postAbsolute(requestUrl, params, (response) => {
this.setEphemeral(ephemeral);
this.handleAuthResponse(response, email, url, authParams, keys);
callback(response);
}.bind(this), function(response){
}, (response) => {
console.error("Registration error", response);
if(typeof response !== 'object') {
response = {error: {message: "A server error occurred while trying to register. Please try again."}};
}
callback(response);
}.bind(this))
}.bind(this));
})
});
}
this.changePassword = function(email, new_password, callback) {
SFJS.crypto.generateInitialEncryptionKeysForUser({password: new_password, email: email}, function(keys, authParams){
this.changePassword = function(email, current_password, new_password, callback) {
SFJS.crypto.generateInitialEncryptionKeysForUser(email, new_password, (keys, authParams) => {
var requestUrl = storageManager.getItem("server") + "/auth/change_pw";
var params = _.merge({new_password: keys.pw}, authParams);
var params = _.merge({current_password: current_password, new_password: keys.pw}, authParams);
httpManager.postAbsolute(requestUrl, params, function(response) {
httpManager.postAbsolute(requestUrl, params, (response) => {
this.handleAuthResponse(response, email, null, authParams, keys);
callback(response);
}.bind(this), function(response){
}, (response) => {
var error = response;
if(!error) {
error = {message: "Something went wrong while changing your password. Your password was not changed. Please try again."}
@@ -232,7 +245,7 @@ angular.module('app')
console.error("Change pw error", response);
callback({error: error});
})
}.bind(this))
})
}
this.updateAuthParams = function(authParams, callback) {
@@ -258,20 +271,10 @@ angular.module('app')
return;
}
if(this.protocolVersion() === "001") {
if(this.keys().ak) {
// upgrade to 002
var authParams = this.getAuthParams();
authParams.version = "002";
this.updateAuthParams(authParams, function(response){
if(!response.error) {
// let rest of UI load first
$timeout(function(){
alert("Your encryption version has been updated. To take full advantage of this update, please resync all your items by clicking Account -> Advanced -> Re-encrypt All Items.")
}, 750);
}
});
}
let latest = SFJS.crypto.version();
if(this.protocolVersion() !== latest) {
// Prompt user to perform security update
}
}

View File

@@ -32,7 +32,10 @@ class DesktopManager {
return this.applicationDataPath;
}
/* Sending a component in its raw state is really slow for the desktop app */
/*
Sending a component in its raw state is really slow for the desktop app
Keys are not passed into ItemParams, so the result is not encrypted
*/
convertComponentForTransmission(component) {
return new ItemParams(component).paramsForExportFile(true);
}

View File

@@ -28,7 +28,7 @@ angular.module('app')
this.unlock = function(passcode, callback) {
var params = this.passcodeAuthParams();
SFJS.crypto.computeEncryptionKeysForUser(_.merge({password: passcode}, params), function(keys){
SFJS.crypto.computeEncryptionKeysForUser(passcode, params, (keys) => {
if(keys.pw !== params.hash) {
callback(false);
return;
@@ -38,26 +38,24 @@ angular.module('app')
this.decryptLocalStorage(keys);
this._locked = false;
callback(true);
}.bind(this));
});
}
this.setPasscode = (passcode, callback) => {
var cost = SFJS.crypto.defaultPasswordGenerationCost();
var salt = SFJS.crypto.generateRandomKey(512);
var defaultParams = {pw_cost: cost, pw_salt: salt, version: "002"};
var uuid = SFJS.crypto.generateUUID();
SFJS.crypto.computeEncryptionKeysForUser(_.merge({password: passcode}, defaultParams), function(keys) {
defaultParams.hash = keys.pw;
SFJS.crypto.generateInitialEncryptionKeysForUser(uuid, passcode, (keys, authParams) => {
authParams.hash = keys.pw;
this._keys = keys;
this._hasPasscode = true;
// Encrypting will initially clear localStorage
this.encryptLocalStorage(keys);
this.encryptLocalStorage(keys, authParams);
// After it's cleared, it's safe to write to it
storageManager.setItem("offlineParams", JSON.stringify(defaultParams), StorageManager.Fixed);
storageManager.setItem("offlineParams", JSON.stringify(authParams), StorageManager.Fixed);
callback(true);
}.bind(this));
});
}
this.changePasscode = (newPasscode, callback) => {
@@ -71,7 +69,7 @@ angular.module('app')
this._hasPasscode = false;
}
this.encryptLocalStorage = function(keys) {
this.encryptLocalStorage = function(keys, authParams) {
storageManager.setKeys(keys);
// Switch to Ephemeral storage, wiping Fixed storage
// Last argument is `force`, which we set to true because in the case of changing passcode

View File

@@ -142,8 +142,9 @@ class StorageManager {
return hash;
}
setKeys(keys) {
setKeys(keys, authParams) {
this.encryptedStorageKeys = keys;
this.encryptedStorageAuthParams = authParams;
}
writeEncryptedStorageToDisk() {
@@ -152,7 +153,7 @@ class StorageManager {
encryptedStorage.storage = this.storageAsHash();
// Save new encrypted storage in Fixed storage
var params = new ItemParams(encryptedStorage, this.encryptedStorageKeys);
var params = new ItemParams(encryptedStorage, this.encryptedStorageKeys, this.encryptedStorageAuthParams.version);
this.setItem("encryptedStorage", JSON.stringify(params.paramsForSync()), StorageManager.Fixed);
}