Skip to content

Commit bde79e9

Browse files
authored
fix: clear sensitive data in memory (#704)
1 parent bf36ba5 commit bde79e9

File tree

19 files changed

+236
-91
lines changed

19 files changed

+236
-91
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,15 @@
11
export function bytesToBase64(bytes: number[]): string {
22
return Buffer.from(bytes).toString('base64');
33
}
4+
5+
export function base64ToBytes(base64: string): number[] {
6+
return Array.from(Buffer.from(base64, 'base64'));
7+
}
8+
9+
export function stringFromBase64(base64: string): string {
10+
return Buffer.from(base64, 'base64').toString('utf-8');
11+
}
12+
13+
export function stringToBase64(str: string): string {
14+
return Buffer.from(str).toString('base64');
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import crypto from 'crypto';
2+
3+
export function generateRandomHex(): string {
4+
return crypto.randomBytes(32).toString('hex');
5+
}

packages/adena-extension/src/components/molecules/web-private-key-box/index.tsx

+31-5
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1-
import React from 'react';
21
import { WebTextarea } from '@components/atoms/web-textarea';
2+
import { useEffect, useState } from 'react';
33
import styled from 'styled-components';
44

5+
import { stringFromBase64 } from '@common/utils/encoding-util';
6+
import { generateRandomHex } from '@common/utils/rand-utils';
57
import { View } from '../../atoms';
68

79
interface WebPrivateKeyBoxProps {
@@ -11,7 +13,7 @@ interface WebPrivateKeyBoxProps {
1113
error?: boolean;
1214
}
1315

14-
const StyledContainer = styled(View) <{ showBlur: boolean }>`
16+
const StyledContainer = styled(View)<{ showBlur: boolean }>`
1517
position: relative;
1618
overflow: hidden;
1719
height: 80px;
@@ -27,17 +29,41 @@ const StyledBlurScreen = styled(View)`
2729
border-radius: 10px;
2830
`;
2931

30-
export const WebPrivateKeyBox = ({ privateKey, showBlur = true, readOnly = false, error = false }: WebPrivateKeyBoxProps): JSX.Element => {
32+
export const WebPrivateKeyBox = ({
33+
privateKey,
34+
showBlur = true,
35+
readOnly = false,
36+
error = false,
37+
}: WebPrivateKeyBoxProps): JSX.Element => {
38+
const randomHexString = generateRandomHex();
39+
const [displayPrivateKey, setDisplayPrivateKey] = useState<string>(randomHexString);
40+
41+
useEffect(() => {
42+
if (!showBlur) {
43+
setDisplayPrivateKey(stringFromBase64(privateKey));
44+
return;
45+
}
46+
47+
setDisplayPrivateKey(randomHexString);
48+
}, [showBlur, privateKey]);
49+
50+
useEffect(() => {
51+
return () => {
52+
setDisplayPrivateKey('');
53+
};
54+
}, []);
3155

3256
return (
3357
<StyledContainer showBlur={showBlur}>
3458
<WebTextarea
35-
value={privateKey}
59+
value={displayPrivateKey}
3660
placeholder='Private Key'
3761
readOnly={readOnly}
3862
error={error}
3963
style={{ height: '100%' }}
40-
onChange={(): void => { return; }}
64+
onChange={(): void => {
65+
return;
66+
}}
4167
spellCheck={false}
4268
/>
4369
{showBlur && <StyledBlurScreen />}

packages/adena-extension/src/components/molecules/web-seed-box/index.tsx

+14-5
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
import { useEffect, useRef } from 'react';
1+
import { stringFromBase64 } from '@common/utils/encoding-util';
2+
import { useEffect, useMemo, useRef } from 'react';
23
import styled from 'styled-components';
34

45
interface WebSeedBoxProps {
5-
seeds: string[];
6+
seedString: string;
67
showBlur?: boolean;
78
}
89

@@ -31,12 +32,20 @@ const BOX_WIDTH = 185;
3132
const BOX_HEIGHT = 40;
3233
const NUMBER_BOX_WIDTH = 40;
3334

34-
export const WebSeedBox = ({ seeds, showBlur = true }: WebSeedBoxProps): JSX.Element => {
35+
export const WebSeedBox = ({ seedString, showBlur = true }: WebSeedBoxProps): JSX.Element => {
3536
const canvasRefs = useRef<(HTMLCanvasElement | null)[]>([]);
3637

38+
const seedSize = useMemo(() => {
39+
return `${stringFromBase64(seedString)}`.split(' ').length;
40+
}, [seedString]);
41+
3742
useEffect(() => {
43+
if (seedSize === 0) return;
44+
3845
const dpr = window?.devicePixelRatio || 1;
3946

47+
const seeds = `${stringFromBase64(seedString)}`.split(' ');
48+
4049
seeds.forEach((seed, index) => {
4150
const canvas = canvasRefs.current[index];
4251
if (!canvas) return;
@@ -87,11 +96,11 @@ export const WebSeedBox = ({ seeds, showBlur = true }: WebSeedBoxProps): JSX.Ele
8796

8897
ctx.scale(dpr, dpr);
8998
});
90-
}, [seeds, showBlur, window?.devicePixelRatio]);
99+
}, [seedSize, seedString, showBlur, window?.devicePixelRatio]);
91100

92101
return (
93102
<CanvasContainer>
94-
{seeds.map((_, index) => (
103+
{Array.from({ length: seedSize }).map((_, index) => (
95104
<CanvasWrapper key={index} blur={showBlur}>
96105
<canvas
97106
ref={(el): HTMLCanvasElement | null => (canvasRefs.current[index] = el)}

packages/adena-extension/src/components/molecules/web-seed-box/web-seed-box.spec.tsx

+1-3
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,14 @@ import theme from '@styles/theme';
66
import { GlobalWebStyle } from '@styles/global-style';
77
import { WebSeedBox } from '.';
88

9-
const seeds = ['seed', 'seed', 'seed', 'seed'];
10-
119
describe('WebSeedBox Component', () => {
1210
it('WebSeedBox render', () => {
1311

1412
render(
1513
<RecoilRoot>
1614
<GlobalWebStyle />
1715
<ThemeProvider theme={theme}>
18-
<WebSeedBox seeds={seeds} />
16+
<WebSeedBox seedString='' />
1917
</ThemeProvider>
2018
</RecoilRoot>,
2119
);

packages/adena-extension/src/components/molecules/web-seed-box/web-seed-box.stories.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ export default {
88

99
export const Default: StoryObj<typeof WebSeedBox> = {
1010
args: {
11-
seeds: ['seed', 'seed', 'seed', 'seed'],
11+
seedString: '',
1212
showBlur: true,
1313
},
1414
};

packages/adena-extension/src/hooks/certify/use-create-password.ts

+11-5
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
1-
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
21
import { PasswordValidationError } from '@common/errors';
3-
import { useAdenaContext } from '@hooks/use-context';
2+
import { evaluatePassword, EvaluatePasswordResult } from '@common/utils/password-utils';
43
import {
54
validateEmptyPassword,
65
validateNotMatchConfirmPassword,
76
validatePasswordComplexity,
87
} from '@common/validation';
9-
import { AdenaWallet } from 'adena-module';
108
import useAppNavigate from '@hooks/use-app-navigate';
11-
import { CreateAccountState, GoogleState, LedgerState, SeedState, RoutePath } from '@types';
12-
import { evaluatePassword, EvaluatePasswordResult } from '@common/utils/password-utils';
9+
import { useAdenaContext } from '@hooks/use-context';
10+
import { CreateAccountState, GoogleState, LedgerState, RoutePath, SeedState } from '@types';
11+
import { AdenaWallet } from 'adena-module';
12+
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
1313

1414
export type UseCreatePasswordReturn = {
1515
pwdState: {
@@ -169,6 +169,7 @@ export const useCreatePassword = (): UseCreatePasswordReturn => {
169169
});
170170
await accountService.changeCurrentAccount(wallet.currentAccount);
171171
await walletService.changePassword(pwd);
172+
clearPassword();
172173
} catch (error) {
173174
console.error(error);
174175
return 'FAIL';
@@ -183,6 +184,7 @@ export const useCreatePassword = (): UseCreatePasswordReturn => {
183184
const wallet = await AdenaWallet.createByWeb3Auth(googleState.privateKey);
184185
await accountService.changeCurrentAccount(wallet.currentAccount);
185186
await walletService.saveWallet(wallet, pwd);
187+
clearPassword();
186188
} catch (error) {
187189
console.error(error);
188190
return 'FAIL';
@@ -213,6 +215,10 @@ export const useCreatePassword = (): UseCreatePasswordReturn => {
213215
setActivated(true);
214216
};
215217

218+
const clearPassword = (): void => {
219+
setInputs({ pwd: '', confirmPwd: '' });
220+
};
221+
216222
return {
217223
pwdState: {
218224
value: pwd,

packages/adena-extension/src/hooks/web/common/use-create-password-screen.ts

+18-10
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
21
import { AdenaWallet } from 'adena-module';
2+
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
33

4-
import { RoutePath } from '@types';
54
import { PasswordValidationError } from '@common/errors';
5+
import { evaluatePassword, EvaluatePasswordResult } from '@common/utils/password-utils';
66
import {
77
validateEmptyPassword,
88
validateNotMatchConfirmPassword,
@@ -13,7 +13,7 @@ import { useAdenaContext } from '@hooks/use-context';
1313
import useIndicatorStep, {
1414
UseIndicatorStepReturn,
1515
} from '@hooks/wallet/broadcast-transaction/use-indicator-step';
16-
import { evaluatePassword, EvaluatePasswordResult } from '@common/utils/password-utils';
16+
import { RoutePath } from '@types';
1717

1818
export type UseCreatePasswordScreenReturn = {
1919
passwordState: {
@@ -41,6 +41,7 @@ export type UseCreatePasswordScreenReturn = {
4141
};
4242
indicatorInfo: UseIndicatorStepReturn;
4343
onKeyDown: (e: React.KeyboardEvent<HTMLInputElement>) => void;
44+
clearPassword: () => void;
4445
};
4546

4647
export const useCreatePasswordScreen = (): UseCreatePasswordScreenReturn => {
@@ -126,11 +127,16 @@ export const useCreatePasswordScreen = (): UseCreatePasswordScreenReturn => {
126127
return false;
127128
};
128129

130+
const clearPassword = (): void => {
131+
setInputs({ password: '', confirmPassword: '' });
132+
};
133+
129134
const _saveWalletByPassword = async (password: string): Promise<void> => {
130135
const { serializedWallet } = params;
131136
const wallet = await AdenaWallet.deserialize(serializedWallet, '');
132137
await walletService.saveWallet(wallet, password);
133138
await accountService.changeCurrentAccount(wallet.currentAccount);
139+
await setInputs({ password: '', confirmPassword: '' });
134140
};
135141

136142
const onChangePassword = useCallback(
@@ -157,14 +163,15 @@ export const useCreatePasswordScreen = (): UseCreatePasswordScreenReturn => {
157163
};
158164

159165
const onKeyDownInput = useCallback(
160-
() => (e: React.KeyboardEvent<HTMLInputElement>): void => {
161-
if (e.key === 'Enter') {
162-
if (disabledCreateButton) {
163-
return;
166+
() =>
167+
(e: React.KeyboardEvent<HTMLInputElement>): void => {
168+
if (e.key === 'Enter') {
169+
if (disabledCreateButton) {
170+
return;
171+
}
172+
onClickCreateButton();
164173
}
165-
onClickCreateButton();
166-
}
167-
},
174+
},
168175
[disabledCreateButton, onClickCreateButton],
169176
);
170177

@@ -207,5 +214,6 @@ export const useCreatePasswordScreen = (): UseCreatePasswordScreenReturn => {
207214
disabled: disabledCreateButton,
208215
},
209216
onKeyDown: onKeyDownInput,
217+
clearPassword,
210218
};
211219
};

packages/adena-extension/src/hooks/web/use-account-import-screen.ts

+2
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,7 @@ const useAccountImportScreen = ({ wallet }: { wallet: Wallet }): UseAccountImpor
189189
const { account, keyring } = result;
190190
const resultWallet = await addAccountWith(wallet.clone(), keyring, account);
191191
await updateWallet(resultWallet);
192+
setInputValue('');
192193
}).then(() => navigate(RoutePath.WebAccountAddedComplete));
193194
return;
194195
} else {
@@ -224,6 +225,7 @@ const useAccountImportScreen = ({ wallet }: { wallet: Wallet }): UseAccountImpor
224225
}
225226

226227
await updateWallet(resultWallet);
228+
setInputValue('');
227229
navigate(RoutePath.WebAccountAddedComplete);
228230
}
229231
}, [

packages/adena-extension/src/hooks/web/use-wallet-create-screen.ts

+12-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { AdenaWallet, HDWalletKeyring, SeedAccount } from 'adena-module';
22
import { useCallback, useMemo, useState } from 'react';
33

4+
import { stringFromBase64, stringToBase64 } from '@common/utils/encoding-util';
45
import useAppNavigate from '@hooks/use-app-navigate';
56
import { useWalletContext } from '@hooks/use-context';
67
import { useCurrentAccount } from '@hooks/use-current-account';
@@ -42,7 +43,9 @@ const useWalletCreateScreen = (): UseWalletCreateReturn => {
4243
hasQuestionnaire: true,
4344
});
4445

45-
const seeds = useMemo(() => AdenaWallet.generateMnemonic(), []);
46+
const seeds = useMemo(() => {
47+
return stringToBase64(AdenaWallet.generateMnemonic());
48+
}, []);
4649

4750
const onClickGoBack = useCallback(() => {
4851
if (step === 'INIT') {
@@ -65,7 +68,10 @@ const useWalletCreateScreen = (): UseWalletCreateReturn => {
6568
}
6669
} else if (step === 'GET_SEED_PHRASE') {
6770
if (wallet) {
68-
const keyring = await HDWalletKeyring.fromMnemonic(seeds);
71+
let rawSeeds = stringFromBase64(seeds);
72+
const keyring = await HDWalletKeyring.fromMnemonic(rawSeeds);
73+
rawSeeds = '';
74+
6975
const account = await SeedAccount.createBy(
7076
keyring,
7177
`Account ${wallet.lastAccountIndex + 1}`,
@@ -85,7 +91,10 @@ const useWalletCreateScreen = (): UseWalletCreateReturn => {
8591
await updateWallet(clone);
8692
navigate(RoutePath.WebAccountAddedComplete);
8793
} else {
88-
const createdWallet = await AdenaWallet.createByMnemonic(seeds);
94+
let rawSeeds = stringFromBase64(seeds);
95+
const createdWallet = await AdenaWallet.createByMnemonic(rawSeeds);
96+
rawSeeds = '';
97+
8998
const serializedWallet = await createdWallet.serialize('');
9099

91100
navigate(RoutePath.WebCreatePassword, {

0 commit comments

Comments
 (0)