Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prevent Javascript from accessing the mnemonic phrase through a private API #948

Merged
merged 1 commit into from
Aug 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions @shared/api/internal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -670,8 +670,8 @@ export const signOut = async (): Promise<{

export const showBackupPhrase = async (
password: string,
): Promise<{ error: string }> => {
let response = { error: "" };
): Promise<{ mnemonicPhrase: string; error: string }> => {
let response = { mnemonicPhrase: "", error: "" };
try {
response = await sendMessageToBackground({
password,
Expand Down
2 changes: 1 addition & 1 deletion extension/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "extension",
"version": "5.3.0",
"version": "5.3.1",
"license": "Apache-2.0",
"prettier": "@stellar/prettier-config",
"scripts": {
Expand Down
4 changes: 2 additions & 2 deletions extension/public/static/manifest/v2.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "Freighter",
"version": "5.3.0",
"version_name": "5.3.0",
"version": "5.3.1",
"version_name": "5.3.1",
"description": "Freighter is a non-custodial wallet extension that enables you to sign Stellar transactions via your browser.",
"browser_specific_settings": {
"gecko": {
Expand Down
4 changes: 2 additions & 2 deletions extension/public/static/manifest/v3.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "Freighter",
"version": "5.3.0",
"version_name": "5.3.0",
"version": "5.3.1",
"version_name": "5.3.1",
"description": "Freighter is a non-custodial wallet extension that enables you to sign Stellar transactions via your browser.",
"background": {
"service_worker": "background.min.js"
Expand Down
25 changes: 21 additions & 4 deletions extension/src/background/messageListener/popupMessageListener.ts
Original file line number Diff line number Diff line change
Expand Up @@ -583,9 +583,23 @@ export const popupMessageListener = (request: Request, sessionStore: Store) => {
};
};

const getMnemonicPhrase = () => ({
mnemonicPhrase: mnemonicPhraseSelector(sessionStore.getState()),
});
const getMnemonicPhrase = async () => {
const { password } = request;

const keyID = (await getIsHardwareWalletActive())
? await _getNonHwKeyID()
: (await localStore.getItem(KEY_ID)) || "";

try {
await _unlockKeystore({ keyID, password });
} catch (e) {
console.error(e);
return { error: "Incorrect password" };
}
return {
mnemonicPhrase: mnemonicPhraseSelector(sessionStore.getState()),
};
};

const confirmMnemonicPhrase = async () => {
const isCorrectPhrase =
Expand Down Expand Up @@ -698,10 +712,13 @@ export const popupMessageListener = (request: Request, sessionStore: Store) => {
keyID: (await localStore.getItem(KEY_ID)) || "",
password,
});
return {};
} catch (e) {
return { error: "Incorrect Password" };
}

return {
mnemonicPhrase: mnemonicPhraseSelector(sessionStore.getState()),
};
};

const _getLocalStorageAccounts = async (password: string) => {
Expand Down
2 changes: 1 addition & 1 deletion extension/src/popup/Router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,7 @@ export const Router = () => {
<GrantAccess />
</PublicKeyRoute>
<PublicKeyRoute path={ROUTES.mnemonicPhrase}>
<MnemonicPhrase />
<MnemonicPhrase mnemonicPhrase="" />
</PublicKeyRoute>
<PublicKeyRoute path={ROUTES.settings} exact>
<Settings />
Expand Down
4 changes: 3 additions & 1 deletion extension/src/popup/components/Onboarding/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ import { BackButton } from "popup/basics/buttons/BackButton";
import "./styles.scss";

export const Onboarding = ({
customBackAction,
hasGoBackBtn,
children,
}: {
customBackAction?: () => void;
hasGoBackBtn?: boolean;
children: React.ReactNode;
}) => {
Expand All @@ -19,7 +21,7 @@ export const Onboarding = ({
<div className="Onboarding">
{hasGoBackBtn && !isNewTabSession ? (
<div className="Onboarding--back">
<BackButton hasBackCopy />
<BackButton customBackAction={customBackAction} hasBackCopy />
</div>
) : null}
{children}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ import { useTranslation } from "react-i18next";

import { InfoBlock } from "popup/basics/InfoBlock";
import { Button } from "popup/basics/buttons/Button";
import { ROUTES } from "popup/constants/routes";
import { navigateTo } from "popup/helpers/navigate";

import {
OnboardingScreen,
Expand All @@ -18,8 +16,10 @@ import "./styles.scss";

export const DisplayMnemonicPhrase = ({
mnemonicPhrase,
setIsConfirmed,
}: {
mnemonicPhrase: string;
setIsConfirmed: (confirmed: boolean) => void;
}) => {
const { t } = useTranslation();

Expand Down Expand Up @@ -60,7 +60,7 @@ export const DisplayMnemonicPhrase = ({
data-testid="display-mnemonic-phrase-next-btn"
fullWidth
onClick={() => {
navigateTo(ROUTES.mnemonicPhraseConfirm);
setIsConfirmed(true);
}}
>
{t("Next")}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ interface generateMnemonicPhraseDisplayProps {
}

export const generateMnemonicPhraseDisplay = ({
mnemonicPhrase,
mnemonicPhrase = "",
}: generateMnemonicPhraseDisplayProps) =>
mnemonicPhrase.split(" ").map((word: string) => {
/*
Expand Down
29 changes: 0 additions & 29 deletions extension/src/popup/helpers/useMnemonicPhrase.ts

This file was deleted.

21 changes: 11 additions & 10 deletions extension/src/popup/views/AccountCreator/index.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import React, { useEffect } from "react";
import React, { useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { Input, Checkbox, TextLink } from "@stellar/design-system";
import { Field, FieldProps, Formik, Form } from "formik";
import { object as YupObject } from "yup";
import { useTranslation } from "react-i18next";

import { showBackupPhrase } from "@shared/api/internal";
import { Button } from "popup/basics/buttons/Button";
import { ROUTES } from "popup/constants/routes";
import { navigateTo } from "popup/helpers/navigate";
import {
password as passwordValidator,
confirmPassword as confirmPasswordValidator,
Expand All @@ -30,13 +29,16 @@ import {
import { Header } from "popup/components/Header";
import { PasswordRequirements } from "popup/components/PasswordRequirements";

import { MnemonicPhrase } from "popup/views/MnemonicPhrase";

import "./styles.scss";

export const AccountCreator = () => {
const publicKey = useSelector(publicKeySelector);
const dispatch = useDispatch();
const authError = useSelector(authErrorSelector);
const { t } = useTranslation();
const [mnemonicPhrase, setMnemonicPhrase] = useState("");

interface FormValues {
password: string;
Expand All @@ -52,6 +54,9 @@ export const AccountCreator = () => {

const handleSubmit = async (values: FormValues) => {
await dispatch(createAccount(values.password));
const res = await showBackupPhrase(values.password);

setMnemonicPhrase(res.mnemonicPhrase);
};

const AccountCreatorSchema = YupObject().shape({
Expand All @@ -60,13 +65,9 @@ export const AccountCreator = () => {
termsOfUse: termsofUseValidator,
});

useEffect(() => {
if (publicKey) {
navigateTo(ROUTES.mnemonicPhrase);
}
}, [publicKey]);

return (
return mnemonicPhrase && publicKey ? (
<MnemonicPhrase mnemonicPhrase={mnemonicPhrase} />
) : (
<>
<FullscreenStyle />
<Header />
Expand Down
4 changes: 2 additions & 2 deletions extension/src/popup/views/DisplayBackupPhrase/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import { ROUTES } from "popup/constants/routes";
import { Button } from "popup/basics/buttons/Button";
import { navigateTo } from "popup/helpers/navigate";
import { emitMetric } from "helpers/metrics";
import { useMnemonicPhrase } from "popup/helpers/useMnemonicPhrase";

import { METRIC_NAMES } from "popup/constants/metricsNames";

Expand All @@ -24,7 +23,7 @@ export const DisplayBackupPhrase = () => {
const { t } = useTranslation();
const [errorMessage, setErrorMessage] = useState("");
const [isPhraseUnlocked, setIsPhraseUnlocked] = useState(false);
const mnemonicPhrase = useMnemonicPhrase();
const [mnemonicPhrase, setMnemonicPhrase] = useState("");

useEffect(() => {
emitMetric(
Expand All @@ -51,6 +50,7 @@ export const DisplayBackupPhrase = () => {
error_type: res.error,
});
} else {
setMnemonicPhrase(res.mnemonicPhrase);
setIsPhraseUnlocked(true);
setErrorMessage("");
emitMetric(METRIC_NAMES.backupPhraseSuccess);
Expand Down
52 changes: 29 additions & 23 deletions extension/src/popup/views/MnemonicPhrase.tsx
Original file line number Diff line number Diff line change
@@ -1,46 +1,52 @@
import React from "react";
import React, { useState } from "react";
import { useSelector } from "react-redux";
import shuffle from "lodash/shuffle";
import { Switch, Redirect } from "react-router-dom";
import { Redirect } from "react-router-dom";

import { APPLICATION_STATE } from "@shared/constants/applicationState";

import { PublicKeyRoute } from "popup/Router";
import { ROUTES } from "popup/constants/routes";
import { FullscreenStyle } from "popup/components/FullscreenStyle";
import { useMnemonicPhrase } from "popup/helpers/useMnemonicPhrase";
import { Header } from "popup/components/Header";
import { Onboarding } from "popup/components/Onboarding";
import { ConfirmMnemonicPhrase } from "popup/components/mnemonicPhrase/ConfirmMnemonicPhrase";
import { DisplayMnemonicPhrase } from "popup/components/mnemonicPhrase/DisplayMnemonicPhrase";
import { applicationStateSelector } from "popup/ducks/accountServices";

export const MnemonicPhrase = () => {
const mnemonicPhrase = useMnemonicPhrase();
interface MnemonicPhraseProps {
mnemonicPhrase: string;
}

export const MnemonicPhrase = ({
mnemonicPhrase = "",
}: MnemonicPhraseProps) => {
const applicationState = useSelector(applicationStateSelector);
const [isConfirmed, setIsConfirmed] = useState(false);

if (applicationState === APPLICATION_STATE.MNEMONIC_PHRASE_CONFIRMED) {
return <Redirect to={ROUTES.pinExtension} />;
}

if (mnemonicPhrase) {
return (
<Switch>
<PublicKeyRoute exact path={ROUTES.mnemonicPhrase}>
<Header />
<FullscreenStyle />
<Onboarding>
<DisplayMnemonicPhrase mnemonicPhrase={mnemonicPhrase} />
</Onboarding>
</PublicKeyRoute>
<PublicKeyRoute exact path={ROUTES.mnemonicPhraseConfirm}>
<Header />
<FullscreenStyle />
<Onboarding hasGoBackBtn>
<ConfirmMnemonicPhrase words={shuffle(mnemonicPhrase.split(" "))} />
</Onboarding>
</PublicKeyRoute>
</Switch>
return isConfirmed ? (
<>
<Header />
<FullscreenStyle />
<Onboarding customBackAction={() => setIsConfirmed(false)} hasGoBackBtn>
<ConfirmMnemonicPhrase words={shuffle(mnemonicPhrase.split(" "))} />
</Onboarding>
</>
) : (
<>
<Header />
<FullscreenStyle />
<Onboarding>
<DisplayMnemonicPhrase
mnemonicPhrase={mnemonicPhrase}
setIsConfirmed={setIsConfirmed}
/>
</Onboarding>
</>
);
}

Expand Down
7 changes: 2 additions & 5 deletions extension/src/popup/views/__tests__/MnemonicPhrase.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,6 @@ jest.mock("popup/constants/history", () => ({
},
}));

jest.mock("popup/helpers/useMnemonicPhrase", () => ({
useMnemonicPhrase: () => "dummy mnemonic",
}));
jest.mock("react-router-dom", () => {
const ReactRouter = jest.requireActual("react-router-dom");
return {
Expand Down Expand Up @@ -68,7 +65,7 @@ describe.skip("MnemonicPhrase", () => {
},
}}
>
<MnemonicPhrase />
<MnemonicPhrase mnemonicPhrase={MNEMONIC} />
</Wrapper>,
);
await waitFor(() => screen.getByTestId("display-mnemonic-phrase"));
Expand Down Expand Up @@ -96,7 +93,7 @@ describe.skip("MnemonicPhrase", () => {
},
}}
>
<MnemonicPhrase />
<MnemonicPhrase mnemonicPhrase={MNEMONIC} />
</Wrapper>,
);

Expand Down
Loading