Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  storage-json.sys.mjs   Sprache: unbekannt

 
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * LoginManagerStorage implementation for the JSON back-end.
 */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  FXA_PWDMGR_HOST: "resource://gre/modules/FxAccountsCommon.sys.mjs",
  FXA_PWDMGR_REALM: "resource://gre/modules/FxAccountsCommon.sys.mjs",
  LoginHelper: "resource://gre/modules/LoginHelper.sys.mjs",
  LoginStore: "resource://gre/modules/LoginStore.sys.mjs",
});

const SYNCABLE_LOGIN_FIELDS = [
  // `nsILoginInfo` fields.
  "hostname",
  "formSubmitURL",
  "httpRealm",
  "username",
  "password",
  "usernameField",
  "passwordField",

  // `nsILoginMetaInfo` fields.
  "timeCreated",
  "timePasswordChanged",
];

// Compares two logins to determine if their syncable fields changed. The login
// manager fires `modifyLogin` for changes to all fields, including ones we
// don't sync. In particular, `timeLastUsed` changes shouldn't mark the login
// for upload; otherwise, we might overwrite changed passwords before they're
// downloaded (bug 973166).
function isSyncableChange(oldLogin, newLogin) {
  oldLogin.QueryInterface(Ci.nsILoginMetaInfo).QueryInterface(Ci.nsILoginInfo);
  newLogin.QueryInterface(Ci.nsILoginMetaInfo).QueryInterface(Ci.nsILoginInfo);
  return SYNCABLE_LOGIN_FIELDS.some(prop => oldLogin[prop] != newLogin[prop]);
}

// Returns true if the argument is for the FxA login.
function isFXAHost(login) {
  return login.hostname == lazy.FXA_PWDMGR_HOST;
}

export class LoginManagerStorage_json {
  constructor() {
    this.__crypto = null; // nsILoginManagerCrypto service
    this.__decryptedPotentiallyVulnerablePasswords = null;
  }

  get _crypto() {
    if (!this.__crypto) {
      this.__crypto = Cc["@mozilla.org/login-manager/crypto/SDR;1"].getService(
        Ci.nsILoginManagerCrypto
      );
    }
    return this.__crypto;
  }

  get _decryptedPotentiallyVulnerablePasswords() {
    if (!this.__decryptedPotentiallyVulnerablePasswords) {
      this._store.ensureDataReady();
      this.__decryptedPotentiallyVulnerablePasswords = [];
      for (const potentiallyVulnerablePassword of this._store.data
        .potentiallyVulnerablePasswords) {
        const decryptedPotentiallyVulnerablePassword = this._crypto.decrypt(
          potentiallyVulnerablePassword.encryptedPassword
        );
        this.__decryptedPotentiallyVulnerablePasswords.push(
          decryptedPotentiallyVulnerablePassword
        );
      }
    }
    return this.__decryptedPotentiallyVulnerablePasswords;
  }

  initialize() {
    try {
      // Force initialization of the crypto module.
      // See bug 717490 comment 17.
      this._crypto;

      let profileDir = Services.dirsvc.get("ProfD", Ci.nsIFile).path;

      // Set the reference to LoginStore synchronously.
      let jsonPath = PathUtils.join(profileDir, "logins.json");
      let backupPath = "";
      let loginsBackupEnabled = Services.prefs.getBoolPref(
        "signon.backup.enabled"
      );
      if (loginsBackupEnabled) {
        backupPath = PathUtils.join(profileDir, "logins-backup.json");
      }
      this._store = new lazy.LoginStore(jsonPath, backupPath);

      return (async () => {
        // Load the data asynchronously.
        this.log(`Opening database at ${this._store.path}.`);
        await this._store.load();
      })().catch(console.error);
    } catch (e) {
      this.log(`Initialization failed ${e.name}.`);
      throw new Error("Initialization failed");
    }
  }

  /**
   * Internal method used by regression tests only.  It is called before
   * replacing this storage module with a new instance.
   */
  terminate() {
    this._store._saver.disarm();
    return this._store._save();
  }

  /**
   * Returns the "sync id" used by Sync to know whether the store is current with
   * respect to the sync servers. It is stored encrypted, but only so we
   * can detect failure to decrypt (for example, a "reset" of the primary
   * password will leave all logins alone, but they will fail to decrypt. We
   * also want this metadata to be unavailable in that scenario)
   *
   * Returns null if the data doesn't exist or if the data can't be
   * decrypted (including if the primary-password prompt is cancelled). This is
   * OK for Sync as it can't even begin syncing if the primary-password is
   * locked as the sync encrytion keys are stored in this login manager.
   */
  async getSyncID() {
    await this._store.load();
    if (!this._store.data.sync) {
      return null;
    }
    let raw = this._store.data.sync.syncID;
    try {
      return raw ? this._crypto.decrypt(raw) : null;
    } catch (e) {
      if (e.result == Cr.NS_ERROR_FAILURE) {
        this.log("Could not decrypt the syncID - returning null.");
        return null;
      }
      // any other errors get re-thrown.
      throw e;
    }
  }

  async setSyncID(syncID) {
    await this._store.load();
    if (!this._store.data.sync) {
      this._store.data.sync = {};
    }
    this._store.data.sync.syncID = syncID ? this._crypto.encrypt(syncID) : null;
    this._store.saveSoon();
  }

  async getLastSync() {
    await this._store.load();
    if (!this._store.data.sync) {
      return 0;
    }
    return this._store.data.sync.lastSync || 0.0;
  }

  async setLastSync(timestamp) {
    await this._store.load();
    if (!this._store.data.sync) {
      this._store.data.sync = {};
    }
    this._store.data.sync.lastSync = timestamp;
    this._store.saveSoon();
  }

  #incrementSyncCounter(login) {
    login.syncCounter++;
  }

  async resetSyncCounter(guid, value) {
    this._store.ensureDataReady();

    // This will also find deleted items.
    let login = this._store.data.logins.find(login => login.guid == guid);
    if (login?.syncCounter > 0) {
      login.syncCounter = Math.max(0, login.syncCounter - value);
      login.everSynced = true;
    }

    this._store.saveSoon();
  }

  // Returns false if the login has marked as deleted or doesn't exist.
  loginIsDeleted(guid) {
    let login = this._store.data.logins.find(l => l.guid == guid);
    return !!login?.deleted;
  }

  // Synrhronuously stores encrypted login, returns login clone with upserted
  // uuid and updated timestamps
  #addLogin(login) {
    this._store.ensureDataReady();

    // Throws if there are bogus values.
    lazy.LoginHelper.checkLoginValues(login);

    // Clone the login, so we don't modify the caller's object.
    let loginClone = login.clone();

    // Initialize the nsILoginMetaInfo fields, unless the caller gave us values
    loginClone.QueryInterface(Ci.nsILoginMetaInfo);
    if (loginClone.guid) {
      let guid = loginClone.guid;
      if (!this._isGuidUnique(guid)) {
        // We have an existing GUID, but it's possible that entry is unable
        // to be decrypted - if that's the case we remove the existing one
        // and allow this one to be added.
        let existing = this._searchLogins({ guid })[0];
        if (this._decryptLogins(existing).length) {
          // Existing item is good, so it's an error to try and re-add it.
          throw new Error("specified GUID already exists");
        }
        // find and remove the existing bad entry.
        let foundIndex = this._store.data.logins.findIndex(l => l.guid == guid);
        if (foundIndex == -1) {
          throw new Error("can't find a matching GUID to remove");
        }
        this._store.data.logins.splice(foundIndex, 1);
      }
    } else {
      loginClone.guid = Services.uuid.generateUUID().toString();
    }

    // Set timestamps
    let currentTime = Date.now();
    if (!loginClone.timeCreated) {
      loginClone.timeCreated = currentTime;
    }
    if (!loginClone.timeLastUsed) {
      loginClone.timeLastUsed = currentTime;
    }
    if (!loginClone.timePasswordChanged) {
      loginClone.timePasswordChanged = currentTime;
    }
    if (!loginClone.timesUsed) {
      loginClone.timesUsed = 1;
    }

    // If the everSynced is already set, then this login is an incoming
    // sync record, so there is no need to mark this as needed to be synced.
    if (!loginClone.everSynced && !isFXAHost(loginClone)) {
      this.#incrementSyncCounter(loginClone);
    }

    this._store.data.logins.push({
      id: this._store.data.nextId++,
      hostname: loginClone.origin,
      httpRealm: loginClone.httpRealm,
      formSubmitURL: loginClone.formActionOrigin,
      usernameField: loginClone.usernameField,
      passwordField: loginClone.passwordField,
      encryptedUsername: loginClone.username,
      encryptedPassword: loginClone.password,
      guid: loginClone.guid,
      encType: this._crypto.defaultEncType,
      timeCreated: loginClone.timeCreated,
      timeLastUsed: loginClone.timeLastUsed,
      timePasswordChanged: loginClone.timePasswordChanged,
      timesUsed: loginClone.timesUsed,
      syncCounter: loginClone.syncCounter,
      everSynced: loginClone.everSynced,
      encryptedUnknownFields: loginClone.unknownFields,
    });
    this._store.saveSoon();

    return loginClone;
  }

  async addLoginsAsync(logins, continueOnDuplicates = false) {
    if (logins.length === 0) {
      return logins;
    }

    const encryptedLogins = await this.#encryptLogins(logins);

    const resultLogins = [];
    for (const [login, encryptedLogin] of encryptedLogins) {
      // check for duplicates
      let loginData = {
        origin: login.origin,
        formActionOrigin: login.formActionOrigin,
        httpRealm: login.httpRealm,
      };
      const existingLogins = await Services.logins.searchLoginsAsync(loginData);

      const matchingLogin = existingLogins.find(l => login.matches(l, true));
      if (matchingLogin) {
        if (continueOnDuplicates) {
          continue;
        } else {
          throw lazy.LoginHelper.createLoginAlreadyExistsError(
            matchingLogin.guid
          );
        }
      }

      const resultLogin = this.#addLogin(encryptedLogin);

      // restore unencrypted username and password for use in `addLogin` event
      // and return value
      resultLogin.username = login.username;
      resultLogin.password = login.password;

      // Send a notification that a login was added.
      lazy.LoginHelper.notifyStorageChanged("addLogin", resultLogin);

      resultLogins.push(resultLogin);
    }

    return resultLogins;
  }

  removeLogin(login, fromSync) {
    this._store.ensureDataReady();

    let [idToDelete, storedLogin] = this._getIdForLogin(login);
    if (!idToDelete) {
      throw new Error("No matching logins");
    }

    let foundIndex = this._store.data.logins.findIndex(l => l.id == idToDelete);
    if (foundIndex != -1) {
      let login = this._store.data.logins[foundIndex];
      if (!login.deleted) {
        if (fromSync) {
          this.#replaceLoginWithTombstone(login);
        } else if (login.everSynced) {
          // The login has been synced, so mark it as deleted.
          this.#incrementSyncCounter(login);
          this.#replaceLoginWithTombstone(login);
        } else {
          // The login was never synced, so just remove it from the data.
          this._store.data.logins.splice(foundIndex, 1);
        }

        this._store.saveSoon();
      }
    }

    lazy.LoginHelper.notifyStorageChanged("removeLogin", storedLogin);
  }

  modifyLogin(oldLogin, newLoginData, fromSync) {
    this._store.ensureDataReady();

    let [idToModify, oldStoredLogin] = this._getIdForLogin(oldLogin);
    if (!idToModify) {
      throw new Error("No matching logins");
    }

    let newLogin = lazy.LoginHelper.buildModifiedLogin(
      oldStoredLogin,
      newLoginData
    );

    // Check if the new GUID is duplicate.
    if (
      newLogin.guid != oldStoredLogin.guid &&
      !this._isGuidUnique(newLogin.guid)
    ) {
      throw new Error("specified GUID already exists");
    }

    // Look for an existing entry in case key properties changed.
    if (!newLogin.matches(oldLogin, true)) {
      let loginData = {
        origin: newLogin.origin,
        formActionOrigin: newLogin.formActionOrigin,
        httpRealm: newLogin.httpRealm,
      };

      let logins = this.searchLogins(
        lazy.LoginHelper.newPropertyBag(loginData)
      );

      let matchingLogin = logins.find(login => newLogin.matches(login, true));
      if (matchingLogin) {
        throw lazy.LoginHelper.createLoginAlreadyExistsError(
          matchingLogin.guid
        );
      }
    }

    // Don't sync changes to the accounts password or when changes were only
    // made to fields that should not be synced.
    if (
      !fromSync &&
      !isFXAHost(newLogin) &&
      isSyncableChange(oldLogin, newLogin)
    ) {
      this.#incrementSyncCounter(newLogin);
    }

    // Get the encrypted value of the username and password.
    let [encUsername, encPassword, encType, encUnknownFields] =
      this._encryptLogin(newLogin);

    for (let loginItem of this._store.data.logins) {
      if (loginItem.id == idToModify && !loginItem.deleted) {
        loginItem.hostname = newLogin.origin;
        loginItem.httpRealm = newLogin.httpRealm;
        loginItem.formSubmitURL = newLogin.formActionOrigin;
        loginItem.usernameField = newLogin.usernameField;
        loginItem.passwordField = newLogin.passwordField;
        loginItem.encryptedUsername = encUsername;
        loginItem.encryptedPassword = encPassword;
        loginItem.guid = newLogin.guid;
        loginItem.encType = encType;
        loginItem.timeCreated = newLogin.timeCreated;
        loginItem.timeLastUsed = newLogin.timeLastUsed;
        loginItem.timePasswordChanged = newLogin.timePasswordChanged;
        loginItem.timesUsed = newLogin.timesUsed;
        loginItem.encryptedUnknownFields = encUnknownFields;
        loginItem.syncCounter = newLogin.syncCounter;
        this._store.saveSoon();
        break;
      }
    }

    lazy.LoginHelper.notifyStorageChanged("modifyLogin", [
      oldStoredLogin,
      newLogin,
    ]);
  }

  // Replace the login with a tombstone. It has a guid and sync-related properties,
  // but does not contain the login or password information.
  #replaceLoginWithTombstone(login) {
    login.deleted = true;

    // Delete all fields except guid, timePasswordChanged, syncCounter
    // and everSynced;
    delete login.hostname;
    delete login.httpRealm;
    delete login.formSubmitURL;
    delete login.usernameField;
    delete login.passwordField;
    delete login.encryptedUsername;
    delete login.encryptedPassword;
    delete login.encType;
    delete login.timeCreated;
    delete login.timeLastUsed;
    delete login.timesUsed;
    delete login.encryptedUnknownFields;
  }

  recordPasswordUse(login) {
    // Update the lastUsed timestamp and increment the use count.
    let propBag = Cc["@mozilla.org/hash-property-bag;1"].createInstance(
      Ci.nsIWritablePropertyBag
    );
    propBag.setProperty("timeLastUsed", Date.now());
    propBag.setProperty("timesUsedIncrement", 1);
    this.modifyLogin(login, propBag);
  }

  async recordBreachAlertDismissal(loginGUID) {
    this._store.ensureDataReady();
    const dismissedBreachAlertsByLoginGUID =
      this._store._data.dismissedBreachAlertsByLoginGUID;

    dismissedBreachAlertsByLoginGUID[loginGUID] = {
      timeBreachAlertDismissed: new Date().getTime(),
    };

    return this._store.saveSoon();
  }

  getBreachAlertDismissalsByLoginGUID() {
    this._store.ensureDataReady();
    return this._store._data.dismissedBreachAlertsByLoginGUID;
  }

  /**
   * Returns an array of nsILoginInfo. If decryption of a login
   * fails due to a corrupt entry, the login is not included in
   * the resulting array.
   *
   * @resolve {nsILoginInfo[]}
   */
  async getAllLogins(includeDeleted) {
    this._store.ensureDataReady();

    let [logins] = this._searchLogins({}, includeDeleted);
    if (!logins.length) {
      return [];
    }

    return this.#decryptLogins(logins);
  }

  async searchLoginsAsync(matchData, includeDeleted) {
    this.log(`Searching for matching logins for origin ${matchData.origin}.`);
    let result = this.searchLogins(
      lazy.LoginHelper.newPropertyBag(matchData),
      includeDeleted
    );
    // Emulate being async:
    return Promise.resolve(result);
  }

  /**
   * Public wrapper around _searchLogins to convert the nsIPropertyBag to a
   * JavaScript object and decrypt the results.
   *
   * @return {nsILoginInfo[]} which are decrypted.
   */
  searchLogins(matchData, includeDeleted) {
    this._store.ensureDataReady();

    let realMatchData = {};
    let options = {};

    matchData.QueryInterface(Ci.nsIPropertyBag2);
    if (matchData.hasKey("guid")) {
      // Enforce GUID-based filtering when available, since the origin of the
      // login may not match the origin of the form in the case of scheme
      // upgrades.
      realMatchData = { guid: matchData.getProperty("guid") };
    } else {
      // Convert nsIPropertyBag to normal JS object.
      for (let prop of matchData.enumerator) {
        switch (prop.name) {
          // Some property names aren't field names but are special options to
          // affect the search.
          case "acceptDifferentSubdomains":
          case "schemeUpgrades":
          case "acceptRelatedRealms":
          case "relatedRealms": {
            options[prop.name] = prop.value;
            break;
          }
          default: {
            realMatchData[prop.name] = prop.value;
            break;
          }
        }
      }
    }

    let [logins] = this._searchLogins(realMatchData, includeDeleted, options);

    // Decrypt entries found for the caller.
    logins = this._decryptLogins(logins);

    return logins;
  }

  /**
   * Private method to perform arbitrary searches on any field. Decryption is
   * left to the caller.
   *
   * formActionOrigin is handled specially for compatibility. If a null string
   * is passed and other match fields are present, it is treated as if it was
   * not present.
   *
   * Returns [logins, ids] for logins that match the arguments, where logins
   * is an array of encrypted nsLoginInfo and ids is an array of associated
   * ids in the database.
   */
  _searchLogins(
    matchData,
    includeDeleted = false,
    aOptions = {
      schemeUpgrades: false,
      acceptDifferentSubdomains: false,
      acceptRelatedRealms: false,
      relatedRealms: [],
    },
    candidateLogins = this._store.data.logins
  ) {
    function match(aLoginItem) {
      for (let field in matchData) {
        let wantedValue = matchData[field];

        // Override the storage field name for some fields due to backwards
        // compatibility with Sync/storage.
        let storageFieldName = field;
        switch (field) {
          case "formActionOrigin": {
            storageFieldName = "formSubmitURL";
            break;
          }
          case "origin": {
            storageFieldName = "hostname";
            break;
          }
        }

        switch (field) {
          case "formActionOrigin":
            if (wantedValue != null) {
              // Historical compatibility requires this special case
              if (
                aLoginItem.formSubmitURL == "" ||
                (wantedValue == "" && Object.keys(matchData).length != 1)
              ) {
                break;
              }
              if (
                !lazy.LoginHelper.isOriginMatching(
                  aLoginItem[storageFieldName],
                  wantedValue,
                  aOptions
                )
              ) {
                return false;
              }
              break;
            }
          // fall through
          case "origin":
            if (wantedValue != null) {
              // needed for formActionOrigin fall through
              if (
                !lazy.LoginHelper.isOriginMatching(
                  aLoginItem[storageFieldName],
                  wantedValue,
                  aOptions
                )
              ) {
                return false;
              }
              break;
            }
          // Normal cases.
          // fall through
          case "httpRealm":
          case "id":
          case "usernameField":
          case "passwordField":
          case "encryptedUsername":
          case "encryptedPassword":
          case "guid":
          case "encType":
          case "timeCreated":
          case "timeLastUsed":
          case "timePasswordChanged":
          case "timesUsed":
          case "syncCounter":
          case "everSynced":
            if (wantedValue == null && aLoginItem[storageFieldName]) {
              return false;
            } else if (aLoginItem[storageFieldName] != wantedValue) {
              return false;
            }
            break;
          // Fail if caller requests an unknown property.
          default:
            throw new Error("Unexpected field: " + field);
        }
      }
      return true;
    }

    let foundLogins = [],
      foundIds = [];
    for (let loginItem of candidateLogins) {
      if (loginItem.deleted && !includeDeleted) {
        continue; // skip deleted items
      }

      if (match(loginItem)) {
        // Create the new nsLoginInfo object, push to array
        let login = Cc["@mozilla.org/login-manager/loginInfo;1"].createInstance(
          Ci.nsILoginInfo
        );
        login.init(
          loginItem.hostname,
          loginItem.formSubmitURL,
          loginItem.httpRealm,
          loginItem.encryptedUsername,
          loginItem.encryptedPassword,
          loginItem.usernameField,
          loginItem.passwordField
        );
        // set nsILoginMetaInfo values
        login.QueryInterface(Ci.nsILoginMetaInfo);
        login.guid = loginItem.guid;
        login.timeCreated = loginItem.timeCreated;
        login.timeLastUsed = loginItem.timeLastUsed;
        login.timePasswordChanged = loginItem.timePasswordChanged;
        login.timesUsed = loginItem.timesUsed;
        login.syncCounter = loginItem.syncCounter;
        login.everSynced = loginItem.everSynced;

        // Any unknown fields along for the ride
        login.unknownFields = loginItem.encryptedUnknownFields;
        foundLogins.push(login);
        foundIds.push(loginItem.id);
      }
    }

    this.log(
      `Returning ${foundLogins.length} logins for specified origin with options ${aOptions}`
    );
    return [foundLogins, foundIds];
  }

  /**
   * Removes all logins from local storage, including FxA Sync key.
   *
   * NOTE: You probably want removeAllUserFacingLogins instead of this function.
   *
   */
  removeAllLogins() {
    this.#removeLogins(false, true);
  }

  /**
   * Removes all user facing logins from storage. e.g. all logins except the FxA Sync key
   *
   * If you need to remove the FxA key, use `removeAllLogins` instead
   *
   * @param fullyRemove remove the logins rather than mark them deleted.
   */
  removeAllUserFacingLogins(fullyRemove) {
    this.#removeLogins(fullyRemove, false);
  }

  /**
   * Removes all logins from storage. If removeFXALogin is true, then the FxA Sync
   * key is also removed.
   *
   * @param fullyRemove remove the logins rather than mark them deleted.
   * @param removeFXALogin also remove the FxA Sync key.
   */
  #removeLogins(fullyRemove, removeFXALogin = false) {
    this._store.ensureDataReady();
    this.log("Removing all logins.");

    let removedLogins = [];
    let remainingLogins = [];
    for (let login of this._store.data.logins) {
      if (
        !removeFXALogin &&
        isFXAHost(login) &&
        login.httpRealm == lazy.FXA_PWDMGR_REALM
      ) {
        remainingLogins.push(login);
      } else {
        removedLogins.push(login);
        if (!fullyRemove && login?.everSynced) {
          // The login has been synced, so mark it as deleted.
          this.#incrementSyncCounter(login);
          this.#replaceLoginWithTombstone(login);
          remainingLogins.push(login);
        }
      }
    }
    this._store.data.logins = remainingLogins;

    this._store.data.potentiallyVulnerablePasswords = [];
    this.__decryptedPotentiallyVulnerablePasswords = null;
    this._store.data.dismissedBreachAlertsByLoginGUID = {};
    this._store.saveSoon();

    lazy.LoginHelper.notifyStorageChanged("removeAllLogins", removedLogins);
  }

  findLogins(origin, formActionOrigin, httpRealm) {
    this._store.ensureDataReady();

    let loginData = {
      origin,
      formActionOrigin,
      httpRealm,
    };
    let matchData = {};
    for (let field of ["origin", "formActionOrigin", "httpRealm"]) {
      if (loginData[field] != "") {
        matchData[field] = loginData[field];
      }
    }
    let [logins] = this._searchLogins(matchData);

    // Decrypt entries found for the caller.
    logins = this._decryptLogins(logins);

    this.log(`Returning ${logins.length} logins.`);
    return logins;
  }

  countLogins(origin, formActionOrigin, httpRealm) {
    this._store.ensureDataReady();

    let loginData = {
      origin,
      formActionOrigin,
      httpRealm,
    };
    let matchData = {};
    for (let field of ["origin", "formActionOrigin", "httpRealm"]) {
      if (loginData[field] != "") {
        matchData[field] = loginData[field];
      }
    }
    let [logins] = this._searchLogins(matchData);

    this.log(`Counted ${logins.length} logins.`);
    return logins.length;
  }

  addPotentiallyVulnerablePassword(login) {
    this._store.ensureDataReady();
    // this breached password is already stored
    if (this.isPotentiallyVulnerablePassword(login)) {
      return;
    }
    this.__decryptedPotentiallyVulnerablePasswords.push(login.password);

    this._store.data.potentiallyVulnerablePasswords.push({
      encryptedPassword: this._crypto.encrypt(login.password),
    });
    this._store.saveSoon();
  }

  isPotentiallyVulnerablePassword(login) {
    return this._decryptedPotentiallyVulnerablePasswords.includes(
      login.password
    );
  }

  clearAllPotentiallyVulnerablePasswords() {
    this._store.ensureDataReady();
    if (!this._store.data.potentiallyVulnerablePasswords.length) {
      // No need to write to disk
      return;
    }
    this._store.data.potentiallyVulnerablePasswords = [];
    this._store.saveSoon();
    this.__decryptedPotentiallyVulnerablePasswords = null;
  }

  get uiBusy() {
    return this._crypto.uiBusy;
  }

  get isLoggedIn() {
    return this._crypto.isLoggedIn;
  }

  /**
   * Returns an array with two items: [id, login]. If the login was not
   * found, both items will be null. The returned login contains the actual
   * stored login (useful for looking at the actual nsILoginMetaInfo values).
   */
  _getIdForLogin(login) {
    this._store.ensureDataReady();

    let matchData = {};
    for (let field of ["origin", "formActionOrigin", "httpRealm"]) {
      if (login[field] != "") {
        matchData[field] = login[field];
      }
    }
    let [logins, ids] = this._searchLogins(matchData);

    let id = null;
    let foundLogin = null;

    // The specified login isn't encrypted, so we need to ensure
    // the logins we're comparing with are decrypted. We decrypt one entry
    // at a time, lest _decryptLogins return fewer entries and screw up
    // indices between the two.
    for (let i = 0; i < logins.length; i++) {
      let [decryptedLogin] = this._decryptLogins([logins[i]]);

      if (!decryptedLogin || !decryptedLogin.equals(login)) {
        continue;
      }

      // We've found a match, set id and break
      foundLogin = decryptedLogin;
      id = ids[i];
      break;
    }

    return [id, foundLogin];
  }

  /**
   * Checks to see if the specified GUID already exists.
   */
  _isGuidUnique(guid) {
    this._store.ensureDataReady();

    return this._store.data.logins.every(l => l.guid != guid);
  }

  /*
   * Asynchronously encrypt multiple logins.
   * Returns a promise resolving to an array of arrays containing two entries:
   * the original login and a clone with encrypted properties.
   */
  async #encryptLogins(logins) {
    if (logins.length === 0) {
      return logins;
    }

    const plaintexts = logins.reduce(
      (memo, { username, password, unknownFields }) =>
        memo.concat([username, password, unknownFields]),
      []
    );
    const ciphertexts = await this._crypto.encryptMany(plaintexts);

    return logins.map((login, i) => {
      const [encryptedUsername, encryptedPassword, encryptedUnknownFields] =
        ciphertexts.slice(3 * i, 3 * i + 3);

      const encryptedLogin = login.clone();
      encryptedLogin.username = encryptedUsername;
      encryptedLogin.password = encryptedPassword;
      encryptedLogin.unknownFields = encryptedUnknownFields;

      return [login, encryptedLogin];
    });
  }

  /*
   * Asynchronously decrypt multiple logins.
   * Returns a promise resolving to an array of clones with decrypted properties.
   */
  async #decryptLogins(logins) {
    if (logins.length === 0) {
      return logins;
    }

    const ciphertexts = logins.reduce(
      (memo, { username, password, unknownFields }) =>
        memo.concat([username, password, unknownFields]),
      []
    );
    const plaintexts = await this._crypto.decryptMany(ciphertexts);

    return logins
      .map((login, i) => {
        // Deleted logins don't have any info to decrypt.
        const decryptedLogin = login.clone();
        if (this.loginIsDeleted(login.guid)) {
          return decryptedLogin;
        }

        const [username, password, unknownFields] = plaintexts.slice(
          3 * i,
          3 * i + 3
        );

        // If the username or password is blank it means that decryption may have
        // failed during decryptMany but we can't differentiate an empty string
        // value from a failure so we attempt to decrypt again and check the
        // result.
        if (!username || !password) {
          try {
            this._crypto.decrypt(login.username);
            this._crypto.decrypt(login.password);
          } catch (e) {
            // If decryption failed (corrupt entry?), just return it as it is.
            // Rethrow other errors (like canceling entry of a primary pw)
            if (e.result == Cr.NS_ERROR_FAILURE) {
              this.log(
                `Could not decrypt login: ${
                  login.QueryInterface(Ci.nsILoginMetaInfo).guid
                }.`
              );
              return null;
            }
            throw e;
          }
        }

        decryptedLogin.username = username;
        decryptedLogin.password = password;
        decryptedLogin.unknownFields = unknownFields;

        return decryptedLogin;
      })
      .filter(Boolean);
  }

  /**
   * Returns the encrypted username, password, and encrypton type for the specified
   * login. Can throw if the user cancels a primary password entry.
   */
  _encryptLogin(login) {
    let encUsername = this._crypto.encrypt(login.username);
    let encPassword = this._crypto.encrypt(login.password);

    // Unknown fields should be encrypted since we can't know whether new fields
    // from other clients will contain sensitive data or not
    let encUnknownFields = null;
    if (login.unknownFields) {
      encUnknownFields = this._crypto.encrypt(login.unknownFields);
    }
    let encType = this._crypto.defaultEncType;

    return [encUsername, encPassword, encType, encUnknownFields];
  }

  /**
   * Decrypts username and password fields in the provided array of
   * logins.
   *
   * The entries specified by the array will be decrypted, if possible.
   * An array of successfully decrypted logins will be returned. The return
   * value should be given to external callers (since still-encrypted
   * entries are useless), whereas internal callers generally don't want
   * to lose unencrypted entries (eg, because the user clicked Cancel
   * instead of entering their primary password)
   */
  _decryptLogins(logins) {
    let result = [];

    for (let login of logins) {
      if (this.loginIsDeleted(login.guid)) {
        result.push(login);
        continue;
      }

      try {
        login.username = this._crypto.decrypt(login.username);
        login.password = this._crypto.decrypt(login.password);
        // Verify unknownFields actually has a value
        if (login.unknownFields) {
          login.unknownFields = this._crypto.decrypt(login.unknownFields);
        }
      } catch (e) {
        // If decryption failed (corrupt entry?), just skip it.
        // Rethrow other errors (like canceling entry of a primary pw)
        if (e.result == Cr.NS_ERROR_FAILURE) {
          continue;
        }
        throw e;
      }
      result.push(login);
    }

    return result;
  }
}

ChromeUtils.defineLazyGetter(LoginManagerStorage_json.prototype, "log", () => {
  let logger = lazy.LoginHelper.createLogger("Login storage");
  return logger.log.bind(logger);
});

[ Dauer der Verarbeitung: 0.38 Sekunden  (vorverarbeitet)  ]

                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge