Skip to content

Commit

Permalink
Require verifying password before chaning it (#1246)
Browse files Browse the repository at this point in the history
* Verify current password before changing it.

* split out verifyPassword into two functions

* typo

* change incorrect password message
  • Loading branch information
mymindstorm authored Aug 2, 2024
1 parent fb8bc84 commit 15667ec
Show file tree
Hide file tree
Showing 3 changed files with 132 additions and 1 deletion.
5 changes: 4 additions & 1 deletion _locales/en/messages.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{
{
"extName": {
"message": "Authenticator",
"description": "Extension Name."
Expand Down Expand Up @@ -515,6 +515,9 @@
"permission_unknown_permission": {
"message": "Unknown permission. If see this message, please send a bug report."
},
"phrase_wrong": {
"message": "Password incorrect"
},
"activate_auto_filter": {
"message": "Warning: Smart filter loosely matches the domain name to an account. Always verify that you are on the correct website before entering a code!"
}
Expand Down
37 changes: 37 additions & 0 deletions src/components/Popup/SetPasswordPage.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
<template>
<div>
<div class="text warning">{{ i18n.security_warning }}</div>
<a-text-input
:label="i18n.current_phrase"
type="password"
v-model="currentPhrase"
v-show="!!defaultEncryption"
/>
<a-text-input :label="i18n.phrase" type="password" v-model="phrase" />
<a-text-input
:label="i18n.confirm_phrase"
Expand All @@ -23,11 +29,13 @@
</template>
<script lang="ts">
import Vue from "vue";
import { verifyPasswordUsingKeyID } from "../../models/password";
export default Vue.extend({
data: function () {
return {
phrase: "",
currentPhrase: "",
confirm: "",
};
},
Expand All @@ -53,10 +61,26 @@ export default Vue.extend({
passwordPolicyHint: function () {
return this.$store.state.menu.passwordPolicyHint;
},
defaultEncryption: function (): string | undefined {
return this.$store.state.accounts.defaultEncryption;
},
},
methods: {
async removePassphrase() {
this.$store.commit("currentView/changeView", "LoadingPage");
if (this.defaultEncryption) {
const isCorrectPassword = await verifyPasswordUsingKeyID(
this.defaultEncryption,
this.currentPhrase
);
if (!isCorrectPassword) {
this.$store.commit("notification/alert", this.i18n.phrase_not_match);
this.$store.commit("currentView/changeView", "SetPasswordPage");
return;
}
}
await this.$store.dispatch("accounts/changePassphrase", "");
this.$store.commit("notification/alert", this.i18n.updateSuccess);
this.$store.commit("style/hideInfo");
Expand All @@ -80,6 +104,19 @@ export default Vue.extend({
}
this.$store.commit("currentView/changeView", "LoadingPage");
if (this.defaultEncryption) {
const isCorrectPassword = await verifyPasswordUsingKeyID(
this.defaultEncryption,
this.currentPhrase
);
if (!isCorrectPassword) {
this.$store.commit("notification/alert", this.i18n.phrase_wrong);
this.$store.commit("currentView/changeView", "SetPasswordPage");
return;
}
}
await this.$store.dispatch("accounts/changePassphrase", this.phrase);
this.$store.commit("notification/alert", this.i18n.updateSuccess);
this.$store.commit("style/hideInfo");
Expand Down
91 changes: 91 additions & 0 deletions src/models/password.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { BrowserStorage, isOldKey } from "./storage";

export async function argonHash(
value: string,
salt: string
): Promise<string | undefined> {
const iframe = document.getElementById("argon-sandbox");
const message = {
action: "hash",
value,
salt,
};

if (!iframe) {
throw new Error("argon-sandbox missing!");
}

const argonPromise: Promise<string | undefined> = new Promise((resolve) => {
window.addEventListener("message", (response) => {
resolve(response.data.response);
});
// @ts-expect-error bad typings
iframe.contentWindow.postMessage(message, "*");
});

return argonPromise;
}

export async function argonVerify(
value: string,
hash: string
): Promise<boolean> {
const iframe = document.getElementById("argon-sandbox");
const message = {
action: "verify",
value,
hash,
};

if (!iframe) {
throw new Error("argon-sandbox missing!");
}

const argonPromise: Promise<boolean> = new Promise((resolve) => {
window.addEventListener("message", (response) => {
resolve(response.data.response);
});
// @ts-expect-error bad typings
iframe.contentWindow.postMessage(message, "*");
});

return argonPromise;
}

// Verify a password using keys in BrowserStorage
export async function verifyPasswordUsingKeyID(
keyId: string,
password: string
): Promise<boolean> {
// Get key for current encryption
const keys = await BrowserStorage.getKeys();
if (isOldKey(keys)) {
throw new Error(
"v3 encryption not being used with verifyPassword. This should never happen!"
);
}

const key = keys.find((key) => key.id === keyId);
if (!key) {
throw new Error(`Key ${keyId} not in BrowserStorage`);
}

return verifyPasswordUsingKey(key, password);
}

export async function verifyPasswordUsingKey(
key: Key,
password: string
): Promise<boolean> {
// Hash password with argon
const rawHash = await argonHash(password, key.salt);
if (!rawHash) {
throw new Error("argon2 did not return a hash!");
}
// https://passlib.readthedocs.io/en/stable/lib/passlib.hash.argon2.html#format-algorithm
const possibleHash = rawHash.split("$")[5];

// verify user password by comparing their password hash with the
// hash of their password's hash
return await argonVerify(possibleHash, key.hash);
}

0 comments on commit 15667ec

Please sign in to comment.