diff --git a/app/assets/javascripts/app/directives/views/accountMenu.js b/app/assets/javascripts/app/directives/views/accountMenu.js index c72f59b16..da31ab353 100644 --- a/app/assets/javascripts/app/directives/views/accountMenu.js +++ b/app/assets/javascripts/app/directives/views/accountMenu.js @@ -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 diff --git a/app/assets/javascripts/app/models/local/itemParams.js b/app/assets/javascripts/app/models/local/itemParams.js index 87f91df9e..04eab536d 100644 --- a/app/assets/javascripts/app/models/local/itemParams.js +++ b/app/assets/javascripts/app/models/local/itemParams.js @@ -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); diff --git a/app/assets/javascripts/app/services/authManager.js b/app/assets/javascripts/app/services/authManager.js index 03bd61eb2..a00e2a1cd 100644 --- a/app/assets/javascripts/app/services/authManager.js +++ b/app/assets/javascripts/app/services/authManager.js @@ -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 } } diff --git a/app/assets/javascripts/app/services/desktopManager.js b/app/assets/javascripts/app/services/desktopManager.js index 677841042..29c370150 100644 --- a/app/assets/javascripts/app/services/desktopManager.js +++ b/app/assets/javascripts/app/services/desktopManager.js @@ -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); } diff --git a/app/assets/javascripts/app/services/passcodeManager.js b/app/assets/javascripts/app/services/passcodeManager.js index 55e9df171..df884e7b7 100644 --- a/app/assets/javascripts/app/services/passcodeManager.js +++ b/app/assets/javascripts/app/services/passcodeManager.js @@ -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 diff --git a/app/assets/javascripts/app/services/storageManager.js b/app/assets/javascripts/app/services/storageManager.js index 9ff44f638..957fbd694 100644 --- a/app/assets/javascripts/app/services/storageManager.js +++ b/app/assets/javascripts/app/services/storageManager.js @@ -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); }