Skip to content

Releases: zeruniverse/Password-Manager

15.01 Release

07 Jun 14:31

Choose a tag to compare

Updates:

  1. Add a mechanism to avoid duplicated IVs

To upgrade from v15.00, just download release zip and overwrite code. Database format is not changed.

15.00 Release

26 May 07:20
56d962c

Choose a tag to compare

Breaking changes from 11.xx / 13.xx

  • Algorithm changed from AES-CBC to AES-GCM with username used as AAD
  • Backup without username problem fixed
  • Fixed QR code not able to import.

To upgrade from 11.xx or 13.xx, follow these steps:

  1. Generate backup.txt from your existing deployment.
  2. Check if key user is in backup.txt, if not, please manually add it to the JSON, it should be "user": " <your_login_username>. Due to a bug, some 11.xx / 13.xx versions will generate backup files without user key.
  3. Go to recovery page of your existing deployment and recover the backup.txt, then export raw.
  4. Deploy the 15.00 version and truncate all tables in the database. (if you disabled signup, you might have to temporary enable it now)
  5. Sign-up in the new deployment, and import the raw file.
  6. Very important: delete the raw file.

I don't plan to make any more breaking changes (i.e. require export / import) in at least next two years.

13.01 Release

25 May 14:41
835764e

Choose a tag to compare

  • Fixed hotkey a

You have to first upgrade to 13.00. The 13.00 version includes breaking changes.

13.00 Release

25 May 13:40

Choose a tag to compare

ALL USERS ARE REQUIRED TO UPGRADE TO THIS VERSION!

One minor security flaw has been observed in 11.xx versions. It has been fixed in bbb83cd If you use strong master password, this issue shouldn't impact safety of your password. But you should upgrade as soon as possible.

This upgrade is not backward compatible, you can either export / clear database / re-import, or use following:

  1. Back up your password. This is very important in case your upgrade fails.
  2. Upgrade to 11.09 and login to your 11.09 password manager. If you are now at 11.xx, upgrading to 11.09 does not require database migration.
  3. After login into the 11.09 password manager (where you can see all password entries), open browser developer tool and go to console, paste following scripts and run.
(async function migratePromiseSaltBugToFixedKdf() {
  "use strict";

  /*
   * Run this on the OLD/BROKEN deployment, after the user has logged in
   * and the vault page has loaded.
   *
   * It migrates the vault from:
   *   legacy secretkey = SgenerateKeyWithSalt(reduceInfo(password), PromiseObject)
   *
   * to:
   *   fixedDerivedSalt = WgenerateKeyWithSalt(user, globalSalt1)
   *   fixed secretkey = SgenerateKeyWithSalt(reduceInfo(password), fixedDerivedSalt)
   *
   * It keeps the same master password.
   */

  function fail(message) {
    throw new Error("[PM KDF migration] " + message);
  }

  function assertRuntime() {
    if (typeof EncryptionWrapper !== "function") {
      fail("EncryptionWrapper is not available. Run this on the password manager frontend page.");
    }

    if (typeof AccountBackend !== "function") {
      fail("AccountBackend is not available. Run this on the logged-in vault page.");
    }

    if (typeof pmBackendConfig !== "function") {
      fail("pmBackendConfig is not available. frontend_routes.js may not be loaded.");
    }

    if (typeof PBKDF2_SHA512 !== "function" || typeof SHA512 !== "function") {
      fail("crypto helpers are not available.");
    }
  }

  async function fixedGenerateSecretKey(wrapper, password, user) {
    const derivedSalt = await EncryptionWrapper.WgenerateKeyWithSalt(
      user,
      wrapper.jsSalt
    );

    return EncryptionWrapper.SgenerateKeyWithSalt(
      EncryptionWrapper.reduceInfo(password, wrapper.alphabet),
      derivedSalt
    );
  }

  function clearLocalPinStateOnly() {
    /*
     * changeuserpw.php deletes server-side PIN records.
     * Clear local PIN material too, otherwise old PIN login state is stale.
     * Keep username cookie because it is harmless/useful for normal login.
     */
    try {
      localStorage.removeItem("pinsalt");
      localStorage.removeItem("en_login_sec");
      localStorage.removeItem("en_login_conf");

      if (typeof deleteCookie === "function") {
        deleteCookie("device");
      }
    } catch (err) {
      console.warn("[PM KDF migration] Could not clear local PIN state:", err);
    }
  }

  assertRuntime();

  const masterPassword = window.prompt(
    "Enter the CURRENT master password for this account. It will not be printed to console."
  );

  if (masterPassword === null || masterPassword === "") {
    fail("Cancelled or empty password.");
  }

  console.log("[PM KDF migration] 1/7 Loading and decrypting current vault...");

  const backend = new AccountBackend();

  await backend.loadAccounts();

  const user =
    backend.user ||
    (typeof getCookie === "function" ? getCookie("username") : "");

  if (!user) {
    fail("Could not determine current username.");
  }

  if (!backend.encryptionWrapper || !backend.encryptionWrapper.secretkey) {
    fail("Current vault secret key is missing. Are you logged in?");
  }

  const oldWrapper = backend.encryptionWrapper;
  const oldSecretKey = oldWrapper.secretkey;

  console.log("[PM KDF migration] 2/7 Verifying master password against legacy KDF...");

  const legacySecretKey = await oldWrapper.generateSecretKey(
    masterPassword,
    user,
    false
  );

  if (legacySecretKey !== oldSecretKey) {
    fail("Master password did not match the currently loaded legacy vault key.");
  }

  console.log("[PM KDF migration] 3/7 Deriving fixed KDF secret key...");

  const fixedSecretKey = await fixedGenerateSecretKey(
    oldWrapper,
    masterPassword,
    user
  );

  if (!fixedSecretKey || fixedSecretKey === oldSecretKey) {
    fail("Fixed secret key derivation failed or unexpectedly matched the legacy key.");
  }

  const fixedLoginSignaturePromise = EncryptionWrapper.WgenerateKeyWithSalt(
    fixedSecretKey,
    user
  );

  const fixedConfKeyPromise = EncryptionWrapper.WgenerateKeyWithSalt(
    masterPassword,
    fixedSecretKey
  );

  const fixedResults = await Promise.all([
    fixedLoginSignaturePromise,
    fixedConfKeyPromise
  ]);

  const fixedLoginSignature = fixedResults[0];
  const fixedConfKey = fixedResults[1];

  const fixedWrapper = new EncryptionWrapper(
    fixedSecretKey,
    oldWrapper.jsSalt,
    oldWrapper.pwSalt,
    oldWrapper.alphabet
  );

  fixedWrapper._confkey = fixedConfKey;

  console.log("[PM KDF migration] 4/7 Re-encrypting accounts and file keys...");

  const encryptedAccountPromises = [];

  for (const account of backend.accounts) {
    if (!account) {
      continue;
    }

    encryptedAccountPromises.push(
      account
        .setEncryptionWrapper(fixedWrapper)
        .then(function (updatedAccount) {
          return updatedAccount.getEncrypted(true);
        })
    );
  }

  const encryptedAccounts = await Promise.all(encryptedAccountPromises);

  const accarray = [];

  for (const encryptedAccount of encryptedAccounts) {
    if (
      !encryptedAccount ||
      typeof encryptedAccount.index === "undefined" ||
      encryptedAccount.index === null
    ) {
      fail("Encountered an encrypted account without index.");
    }

    accarray[encryptedAccount.index] = encryptedAccount;
  }

  console.log(
    "[PM KDF migration] 5/7 Sending migrated vault to server. Accounts:",
    encryptedAccounts.length
  );

  const response = await backend.doPost("changeuserpw", {
    newpass: fixedLoginSignature,
    accarray: JSON.stringify(accarray)
  });

  console.log("[PM KDF migration] 6/7 Updating local session storage...");

  if (response && response.password) {
    if (typeof pmSetAuthCredentials === "function") {
      pmSetAuthCredentials(user, response.password);
    } else {
      sessionStorage.pm_auth_user = user;
      sessionStorage.pm_auth_password = String(response.password).toLowerCase();
    }
  }

  await EncryptionWrapper.persistCredentials(
    user,
    fixedSecretKey,
    fixedConfKey,
    oldWrapper.pwSalt
  );

  clearLocalPinStateOnly();

  try {
    if (typeof backend.clearTimeout === "function") {
      backend.clearTimeout();
    }
  } catch (err) {
    console.warn("[PM KDF migration] Could not clear backend timers:", err);
  }

  console.log("[PM KDF migration] 7/7 DONE.");
  console.log(
    "[PM KDF migration] Now deploy the fixed frontend code, hard-refresh the page, and log in with the SAME master password. Re-create PIN login afterward if you use PIN."
  );

  return {
    status: "success",
    user: user,
    migratedAccounts: encryptedAccounts.length
  };
})().catch(function (err) {
  console.error(err);
  alert(String(err && err.message ? err.message : err));
});
  1. Wait for DONE output in console
  2. Logout and deploy 13.00. At this point, you can no longer login in 11.xx.
  3. Clear all cache, session storage and local storage, login into Password Manager 13.00. If the console fix scripts succeed, you should be able to login and see all your old data.

11.09 Release

25 May 11:53

Choose a tag to compare

Updates:

  1. Make backend stateless to support serverless deployment.
  2. Add _headers to frontend to ensure safety

To upgrade from v11.00 or above, just download release zip and overwrite code. Database format is not changed.

11.08 Release

12 May 19:01
ff53f34

Choose a tag to compare

Updates:

  1. Removed E-mail based 2FA for Password-Manager master account
  2. Added support for TOTP based 2FA for Password-Manager master account (This is not client-side encryption, if you lose your phone, you can modify data in database to re-gain access)
  3. Split frontend and backend code so they can be separately deployed. This adds an additional layer of security compared to old integrity check. Be sure to deploy frontend code to somewhere absolutely safe (same as old integrity check html). You can use GitHub Pages or Cloudflare Pages and be sure that your account associated with the static deployment is secure (e.g. enable 2FA and use strong password)

To upgrade from v11.00 or above, just download release zip and overwrite code. Database format is not changed.

11.07 Release

12 May 05:51

Choose a tag to compare

Clean up of docs.

11.06 Release

12 May 05:20

Choose a tag to compare

Updates:

  1. Use secure random
  2. Support management of TOTP MFA

To upgrade from v11.00 or above, just download release zip and overwrite code. Database format is not changed.

11.05 Release

01 Jun 09:42

Choose a tag to compare

Updates:

  1. Fix unexpected session timeout
  2. Change all filename to lower-case so it's compatible with case-insensitive web servers
  3. Allow copy password regardless of whether it's shown.
  4. Support for ipv6
  5. Fix history log for servers behind WAF or CDN

To upgrade from v11.00 or above, just download release zip and overwrite code. Database format is not changed.

11.01 Release

17 Feb 07:40

Choose a tag to compare

Email verification service changed from SendGrid to Gmail. I received complaints about SendGrid not friendly to individual users and thus I changed default / sample email verification method to Gmail. You only need a valid Gmail account to make email verification work. Just set up 2-step verification and generate an app-specific password for this password manager.

Except for above email verification change, this release is same with 11.00. So to upgrade from 11.00, you just need to overwrite source code files.